Merge "PiP: Snap to closest edge when releasing during a pinch-resize." into sc-dev
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index 271129b..c369801 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -108,7 +108,7 @@
                         schemasPackageAccessibleBundles.entrySet()) {
                     List<PackageIdentifier> packageIdentifiers =
                             new ArrayList<>(entry.getValue().size());
-                    for (int i = 0; i < packageIdentifiers.size(); i++) {
+                    for (int i = 0; i < entry.getValue().size(); i++) {
                         packageIdentifiers.add(new PackageIdentifier(entry.getValue().get(i)));
                     }
                     schemasPackageAccessible.put(entry.getKey(), packageIdentifiers);
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
index 8bff720..2f1817e 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
@@ -317,7 +317,7 @@
             }
 
             Map<String, List<PackageIdentifier>> prefixedSchemasPackageAccessible =
-                    new ArrayMap<>(schemasNotPlatformSurfaceable.size());
+                    new ArrayMap<>(schemasPackageAccessible.size());
             for (Map.Entry<String, List<PackageIdentifier>> entry :
                     schemasPackageAccessible.entrySet()) {
                 prefixedSchemasPackageAccessible.put(prefix + entry.getKey(), entry.getValue());
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
index eb1623e..6595d8d 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
@@ -37,8 +37,9 @@
 import java.util.concurrent.Executors;
 
 /**
- * This test class adapts the AppSearch Framework API to ListenableFuture, so it can be tested via
- * a consistent interface.
+ * This test class adapts the AppSearch Framework API to ListenableFuture, so it can be tested via a
+ * consistent interface.
+ *
  * @hide
  */
 public class GlobalSearchSessionShimImpl implements GlobalSearchSessionShim {
@@ -47,7 +48,13 @@
 
     @NonNull
     public static ListenableFuture<GlobalSearchSessionShim> createGlobalSearchSession() {
-        Context context = ApplicationProvider.getApplicationContext();
+        return createGlobalSearchSession(ApplicationProvider.getApplicationContext());
+    }
+
+    /** Only for use when called from a non-instrumented context. */
+    @NonNull
+    public static ListenableFuture<GlobalSearchSessionShim> createGlobalSearchSession(
+            @NonNull Context context) {
         AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class);
         SettableFuture<AppSearchResult<GlobalSearchSession>> future = SettableFuture.create();
         ExecutorService executor = Executors.newCachedThreadPool();
@@ -62,7 +69,6 @@
             @NonNull GlobalSearchSession session, @NonNull ExecutorService executor) {
         mGlobalSearchSession = Preconditions.checkNotNull(session);
         mExecutor = Preconditions.checkNotNull(executor);
-
     }
 
     @NonNull
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 82ee5d8..71fe55f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -212,7 +212,7 @@
     final JobPackageTracker mJobPackageTracker = new JobPackageTracker();
     final JobConcurrencyManager mConcurrencyManager;
 
-    static final int MSG_JOB_EXPIRED = 0;
+    static final int MSG_CHECK_INDIVIDUAL_JOB = 0;
     static final int MSG_CHECK_JOB = 1;
     static final int MSG_STOP_JOB = 2;
     static final int MSG_CHECK_JOB_GREEDY = 3;
@@ -1711,6 +1711,12 @@
             if (DEBUG) {
                 Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
             }
+            JobStatus newJs = mJobs.getJobByUidAndJobId(jobStatus.getUid(), jobStatus.getJobId());
+            if (newJs != null) {
+                // This job was stopped because the app scheduled a new job with the same job ID.
+                // Check if the new job is ready to run.
+                mHandler.obtainMessage(MSG_CHECK_INDIVIDUAL_JOB, newJs).sendToTarget();
+            }
             return;
         }
 
@@ -1748,7 +1754,11 @@
 
     @Override
     public void onRunJobNow(JobStatus jobStatus) {
-        mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
+        if (jobStatus == null) {
+            mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
+        } else {
+            mHandler.obtainMessage(MSG_CHECK_INDIVIDUAL_JOB, jobStatus).sendToTarget();
+        }
     }
 
     final private class JobHandler extends Handler {
@@ -1764,18 +1774,15 @@
                     return;
                 }
                 switch (message.what) {
-                    case MSG_JOB_EXPIRED: {
-                        JobStatus runNow = (JobStatus) message.obj;
-                        // runNow can be null, which is a controller's way of indicating that its
-                        // state is such that all ready jobs should be run immediately.
-                        if (runNow != null) {
-                            if (!isCurrentlyActiveLocked(runNow)
-                                    && isReadyToBeExecutedLocked(runNow)) {
-                                mJobPackageTracker.notePending(runNow);
-                                addOrderedItem(mPendingJobs, runNow, sPendingJobComparator);
+                    case MSG_CHECK_INDIVIDUAL_JOB: {
+                        JobStatus js = (JobStatus) message.obj;
+                        if (js != null) {
+                            if (isReadyToBeExecutedLocked(js)) {
+                                mJobPackageTracker.notePending(js);
+                                addOrderedItem(mPendingJobs, js, sPendingJobComparator);
                             }
                         } else {
-                            queueReadyJobsForExecutionLocked();
+                            Slog.e(TAG, "Given null job to check individually");
                         }
                     } break;
                     case MSG_CHECK_JOB:
@@ -1909,12 +1916,10 @@
         // This method will check and capture all ready jobs, so we don't need to keep any messages
         // in the queue.
         mHandler.removeMessages(MSG_CHECK_JOB_GREEDY);
+        mHandler.removeMessages(MSG_CHECK_INDIVIDUAL_JOB);
         // MSG_CHECK_JOB is a weaker form of _GREEDY. Since we're checking and queueing all ready
         // jobs, we don't need to keep any MSG_CHECK_JOB messages in the queue.
         mHandler.removeMessages(MSG_CHECK_JOB);
-        // This method will capture all expired jobs that are ready, so there's no need to keep
-        // the _EXPIRED messages in the queue.
-        mHandler.removeMessages(MSG_JOB_EXPIRED);
         if (DEBUG) {
             Slog.d(TAG, "queuing all ready jobs for execution:");
         }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
index 50723c7..131a6d4 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
@@ -107,8 +107,6 @@
                         taskStatus.contentObserverJobInstance.mChangedUris.add(uri);
                     }
                 }
-                taskStatus.changedAuthorities = null;
-                taskStatus.changedUris = null;
             }
             taskStatus.changedAuthorities = null;
             taskStatus.changedUris = null;
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 4b7eda0..ed717c4 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -19,7 +19,6 @@
 import android.annotation.IntDef;
 import android.annotation.UserIdInt;
 import android.app.backup.BackupManager;
-import android.app.backup.BackupManager.OperationType;
 import android.app.backup.BackupManagerMonitor;
 import android.app.backup.BackupProgress;
 import android.app.backup.BackupTransport;
@@ -667,7 +666,7 @@
 
         // The rest of the 'list' options work with a restore session on the current transport
         try {
-            mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null, OperationType.BACKUP);
+            mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null);
             if (mRestore == null) {
                 System.err.println(BMGR_ERR_NO_RESTORESESSION_FOR_USER + userId);
                 return;
@@ -822,7 +821,7 @@
 
         try {
             boolean didRestore = false;
-            mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null, OperationType.BACKUP);
+            mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null);
             if (mRestore == null) {
                 System.err.println(BMGR_ERR_NO_RESTORESESSION_FOR_USER + userId);
                 return;
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index 312f4ac..05336ba 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -161,6 +161,12 @@
                                     idmap_path.c_str(), uid));
   }
 
+  // idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap guarantees
+  // that existing memory maps will continue to be valid and unaffected. The file must be deleted
+  // before attempting to create the idmap, so that if idmap  creation fails, the overlay will no
+  // longer be usable.
+  unlink(idmap_path.c_str());
+
   const auto target = GetTargetContainer(target_path);
   if (!target) {
     return error("failed to load target '%s'" + target_path);
@@ -177,10 +183,6 @@
     return error(idmap.GetErrorMessage());
   }
 
-  // idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap guarantees
-  // that existing memory maps will continue to be valid and unaffected.
-  unlink(idmap_path.c_str());
-
   umask(kIdmapFilePermissionMask);
   std::ofstream fout(idmap_path);
   if (fout.fail()) {
diff --git a/config/hiddenapi-unsupported.txt b/config/hiddenapi-unsupported.txt
index 90a526b..48aa8b2 100644
--- a/config/hiddenapi-unsupported.txt
+++ b/config/hiddenapi-unsupported.txt
@@ -204,7 +204,6 @@
 Landroid/os/IUpdateEngine$Stub;-><init>()V
 Landroid/os/IUserManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
 Landroid/os/IUserManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/IUserManager;
-Landroid/os/IVibratorService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/IVibratorService;
 Landroid/os/storage/IObbActionListener$Stub;-><init>()V
 Landroid/os/storage/IStorageManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
 Landroid/os/storage/IStorageManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/storage/IStorageManager;
diff --git a/core/api/current.txt b/core/api/current.txt
index 53be53c..2531d81 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -849,6 +849,7 @@
     field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
     field public static final int keycode = 16842949; // 0x10100c5
     field public static final int killAfterRestore = 16843420; // 0x101029c
+    field public static final int knownCerts = 16844330; // 0x101062a
     field public static final int label = 16842753; // 0x1010001
     field public static final int labelFor = 16843718; // 0x10103c6
     field @Deprecated public static final int labelTextSize = 16843317; // 0x1010235
@@ -10440,6 +10441,7 @@
     field public static final String USAGE_STATS_SERVICE = "usagestats";
     field public static final String USB_SERVICE = "usb";
     field public static final String USER_SERVICE = "user";
+    field public static final String VIBRATOR_MANAGER_SERVICE = "vibrator_manager";
     field public static final String VIBRATOR_SERVICE = "vibrator";
     field public static final String VPN_MANAGEMENT_SERVICE = "vpn_management";
     field public static final String WALLPAPER_SERVICE = "wallpaper";
@@ -10980,6 +10982,7 @@
     field public static final String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS";
     field public static final String EXTRA_INSTALLER_PACKAGE_NAME = "android.intent.extra.INSTALLER_PACKAGE_NAME";
     field public static final String EXTRA_INTENT = "android.intent.extra.INTENT";
+    field public static final String EXTRA_IS_BUBBLED = "android.intent.extra.IS_BUBBLED";
     field public static final String EXTRA_KEY_EVENT = "android.intent.extra.KEY_EVENT";
     field public static final String EXTRA_LOCAL_ONLY = "android.intent.extra.LOCAL_ONLY";
     field public static final String EXTRA_LOCUS_ID = "android.intent.extra.LOCUS_ID";
@@ -23033,10 +23036,12 @@
     method @Deprecated public int getStreamType();
     method public String getTitle(android.content.Context);
     method public float getVolume();
+    method public boolean isHapticGeneratorEnabled();
     method public boolean isLooping();
     method public boolean isPlaying();
     method public void play();
     method public void setAudioAttributes(android.media.AudioAttributes) throws java.lang.IllegalArgumentException;
+    method public boolean setHapticGeneratorEnabled(boolean);
     method public void setLooping(boolean);
     method @Deprecated public void setStreamType(int);
     method public void setVolume(float);
@@ -31625,6 +31630,7 @@
     method @NonNull public int[] areEffectsSupported(@NonNull int...);
     method @NonNull public boolean[] arePrimitivesSupported(@NonNull int...);
     method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel();
+    method public int getId();
     method public abstract boolean hasAmplitudeControl();
     method public abstract boolean hasVibrator();
     method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(long);
@@ -31639,10 +31645,12 @@
   }
 
   public abstract class VibratorManager {
+    method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel();
     method @NonNull public abstract android.os.Vibrator getDefaultVibrator();
     method @NonNull public abstract android.os.Vibrator getVibrator(int);
     method @NonNull public abstract int[] getVibratorIds();
-    method public abstract void vibrate(@NonNull android.os.CombinedVibrationEffect);
+    method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibrationEffect);
+    method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibrationEffect, @Nullable android.os.VibrationAttributes);
   }
 
   public class WorkSource implements android.os.Parcelable {
@@ -42029,8 +42037,9 @@
     field public static final int OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO = 2; // 0x2
     field public static final int OVERRIDE_NETWORK_TYPE_LTE_CA = 1; // 0x1
     field public static final int OVERRIDE_NETWORK_TYPE_NONE = 0; // 0x0
+    field public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 4; // 0x4
     field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA = 3; // 0x3
-    field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4; // 0x4
+    field @Deprecated public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4; // 0x4
   }
 
   public class TelephonyManager {
@@ -53582,16 +53591,19 @@
 
   public class EdgeEffect {
     ctor public EdgeEffect(android.content.Context);
+    ctor public EdgeEffect(@NonNull android.content.Context, @Nullable android.util.AttributeSet);
     method public boolean draw(android.graphics.Canvas);
     method public void finish();
     method @Nullable public android.graphics.BlendMode getBlendMode();
     method @ColorInt public int getColor();
+    method public float getDistance();
     method public int getMaxHeight();
     method public int getType();
     method public boolean isFinished();
     method public void onAbsorb(int);
     method public void onPull(float);
     method public void onPull(float, float);
+    method public float onPullDistance(float, float);
     method public void onRelease();
     method public void setBlendMode(@Nullable android.graphics.BlendMode);
     method public void setColor(@ColorInt int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index dffa0cc..fdd1e66 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -179,6 +179,7 @@
     field public static final String PACKET_KEEPALIVE_OFFLOAD = "android.permission.PACKET_KEEPALIVE_OFFLOAD";
     field public static final String PEERS_MAC_ADDRESS = "android.permission.PEERS_MAC_ADDRESS";
     field public static final String PERFORM_CDMA_PROVISIONING = "android.permission.PERFORM_CDMA_PROVISIONING";
+    field public static final String PERFORM_IMS_SINGLE_REGISTRATION = "android.permission.PERFORM_IMS_SINGLE_REGISTRATION";
     field public static final String PERFORM_SIM_ACTIVATION = "android.permission.PERFORM_SIM_ACTIVATION";
     field public static final String POWER_SAVER = "android.permission.POWER_SAVER";
     field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
@@ -338,9 +339,8 @@
     field public static final int config_helpPackageNameKey = 17039387; // 0x104001b
     field public static final int config_helpPackageNameValue = 17039388; // 0x104001c
     field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
-    field public static final int config_systemAutomotiveProjection = 17039402; // 0x104002a
+    field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029
     field public static final int config_systemGallery = 17039399; // 0x1040027
-    field public static final int config_systemVideoCall = 17039401; // 0x1040029
   }
 
   public static final class R.style {
@@ -2632,6 +2632,7 @@
     field public static final int PROTECTION_FLAG_CONFIGURATOR = 524288; // 0x80000
     field public static final int PROTECTION_FLAG_DOCUMENTER = 262144; // 0x40000
     field public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 1048576; // 0x100000
+    field public static final int PROTECTION_FLAG_KNOWN_SIGNER = 134217728; // 0x8000000
     field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
     field public static final int PROTECTION_FLAG_RECENTS = 33554432; // 0x2000000
     field public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 5d0cea5..632b10f 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -54,9 +54,8 @@
     field public static final int config_defaultAssistant = 17039393; // 0x1040021
     field public static final int config_defaultDialer = 17039395; // 0x1040023
     field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
-    field public static final int config_systemAutomotiveProjection = 17039402; // 0x104002a
+    field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029
     field public static final int config_systemGallery = 17039399; // 0x1040027
-    field public static final int config_systemVideoCall = 17039401; // 0x1040029
   }
 
 }
@@ -694,6 +693,7 @@
     method @Nullable public String getSystemTextClassifierPackageName();
     method @Nullable public String getWellbeingPackageName();
     method public void holdLock(android.os.IBinder, int);
+    method @RequiresPermission(android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES) public void setKeepUninstalledPackages(@NonNull java.util.List<java.lang.String>);
     field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage";
     field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption";
     field public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec";
@@ -1372,6 +1372,32 @@
     field public static final int RESOURCES_SDK_INT;
   }
 
+  public abstract class CombinedVibrationEffect implements android.os.Parcelable {
+    method public abstract long getDuration();
+  }
+
+  public static final class CombinedVibrationEffect.Mono extends android.os.CombinedVibrationEffect {
+    method public long getDuration();
+    method @NonNull public android.os.VibrationEffect getEffect();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Mono> CREATOR;
+  }
+
+  public static final class CombinedVibrationEffect.Sequential extends android.os.CombinedVibrationEffect {
+    method @NonNull public java.util.List<java.lang.Integer> getDelays();
+    method public long getDuration();
+    method @NonNull public java.util.List<android.os.CombinedVibrationEffect> getEffects();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Sequential> CREATOR;
+  }
+
+  public static final class CombinedVibrationEffect.Stereo extends android.os.CombinedVibrationEffect {
+    method public long getDuration();
+    method @NonNull public android.util.SparseArray<android.os.VibrationEffect> getEffects();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Stereo> CREATOR;
+  }
+
   public class DeviceIdleManager {
     method @NonNull public String[] getSystemPowerWhitelist();
     method @NonNull public String[] getSystemPowerWhitelistExceptIdle();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index c242fd4..050f34a 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -9972,6 +9972,15 @@
              * <p>The shortcut activity will be used when the bubble is expanded. This will display
              * the shortcut activity in a floating window over the existing foreground activity.</p>
              *
+             * <p>When the shortcut is displayed in a bubble, there will be an intent
+             * extra set on the activity, {@link Intent#EXTRA_IS_BUBBLED}
+             * with {@code true}. You may check this in the onCreate of your activity via:
+             *
+             * <pre class="prettyprint">
+             * boolean isBubbled = getIntent().getBooleanExtra(Intent.EXTRA_IS_BUBBLED, false);
+             * </pre>
+             * </p>
+             *
              * <p>If the shortcut has not been published when the bubble notification is sent,
              * no bubble will be produced. If the shortcut is deleted while the bubble is active,
              * the bubble will be removed.</p>
@@ -10000,6 +10009,15 @@
              * app content in a floating window over the existing foreground activity. The intent
              * should point to a resizable activity. </p>
              *
+             * <p>When the activity is displayed in a bubble, there will be an intent
+             * extra set on the activity, {@link Intent#EXTRA_IS_BUBBLED}
+             * with {@code true}. You may check this in the onCreate of your activity via:
+             *
+             * <pre class="prettyprint">
+             * boolean isBubbled = getIntent().getBooleanExtra(Intent.EXTRA_IS_BUBBLED, false);
+             * </pre>
+             * </p>
+             *
              * @throws NullPointerException if intent is null.
              * @throws NullPointerException if icon is null.
              */
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 7404e53..d5e9570 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -167,9 +167,11 @@
 import android.os.SystemConfigManager;
 import android.os.SystemUpdateManager;
 import android.os.SystemVibrator;
+import android.os.SystemVibratorManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.Vibrator;
+import android.os.VibratorManager;
 import android.os.health.SystemHealthManager;
 import android.os.image.DynamicSystemManager;
 import android.os.image.IDynamicSystemService;
@@ -699,6 +701,13 @@
                     }
                 });
 
+        registerService(Context.VIBRATOR_MANAGER_SERVICE, VibratorManager.class,
+                new CachedServiceFetcher<VibratorManager>() {
+                    @Override
+                    public VibratorManager createService(ContextImpl ctx) {
+                        return new SystemVibratorManager(ctx);
+                    }});
+
         registerService(Context.VIBRATOR_SERVICE, Vibrator.class,
                 new CachedServiceFetcher<Vibrator>() {
             @Override
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 673de8f..dae565e 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -361,36 +361,7 @@
             try {
                 // All packages, current transport
                 IRestoreSession binder =
-                        sService.beginRestoreSessionForUser(mContext.getUserId(), null, null,
-                                OperationType.BACKUP);
-                if (binder != null) {
-                    session = new RestoreSession(mContext, binder);
-                }
-            } catch (RemoteException e) {
-                Log.e(TAG, "beginRestoreSession() couldn't connect");
-            }
-        }
-        return session;
-    }
-
-    /**
-     * Begin the process of restoring data from backup.  See the
-     * {@link android.app.backup.RestoreSession} class for documentation on that process.
-     *
-     * @param operationType Type of the operation, see {@link OperationType}
-     *
-     * @hide
-     */
-    @RequiresPermission(android.Manifest.permission.BACKUP)
-    public RestoreSession beginRestoreSession(@OperationType int operationType) {
-        RestoreSession session = null;
-        checkServiceBinder();
-        if (sService != null) {
-            try {
-                // All packages, current transport
-                IRestoreSession binder =
-                        sService.beginRestoreSessionForUser(mContext.getUserId(), null, null,
-                                operationType);
+                        sService.beginRestoreSessionForUser(mContext.getUserId(), null, null);
                 if (binder != null) {
                     session = new RestoreSession(mContext, binder);
                 }
@@ -801,7 +772,7 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.BACKUP)
     public int requestBackup(String[] packages, BackupObserver observer) {
-        return requestBackup(packages, observer, null, 0, OperationType.BACKUP);
+        return requestBackup(packages, observer, null, 0);
     }
 
     /**
@@ -826,31 +797,6 @@
     @RequiresPermission(android.Manifest.permission.BACKUP)
     public int requestBackup(String[] packages, BackupObserver observer,
             BackupManagerMonitor monitor, int flags) {
-        return requestBackup(packages, observer, monitor, flags, OperationType.BACKUP);
-    }
-
-    /**
-     * Request an immediate backup, providing an observer to which results of the backup operation
-     * will be published. The Android backup system will decide for each package whether it will
-     * be full app data backup or key/value-pair-based backup.
-     *
-     * <p>If this method returns {@link BackupManager#SUCCESS}, the OS will attempt to backup all
-     * provided packages using the remote transport.
-     *
-     * @param packages List of package names to backup.
-     * @param observer The {@link BackupObserver} to receive callbacks during the backup
-     *                 operation. Could be {@code null}.
-     * @param monitor  The {@link BackupManagerMonitorWrapper} to receive callbacks of important
-     *                 events during the backup operation. Could be {@code null}.
-     * @param flags    {@link #FLAG_NON_INCREMENTAL_BACKUP}.
-     * @param operationType {@link OperationType}
-     * @return {@link BackupManager#SUCCESS} on success; nonzero on error.
-     * @throws IllegalArgumentException on null or empty {@code packages} param.
-     * @hide
-     */
-    @RequiresPermission(android.Manifest.permission.BACKUP)
-    public int requestBackup(String[] packages, BackupObserver observer,
-            BackupManagerMonitor monitor, int flags, @OperationType int operationType) {
         checkServiceBinder();
         if (sService != null) {
             try {
@@ -860,8 +806,7 @@
                 BackupManagerMonitorWrapper monitorWrapper = monitor == null
                         ? null
                         : new BackupManagerMonitorWrapper(monitor);
-                return sService.requestBackup(packages, observerWrapper, monitorWrapper, flags,
-                        operationType);
+                return sService.requestBackup(packages, observerWrapper, monitorWrapper, flags);
             } catch (RemoteException e) {
                 Log.e(TAG, "requestBackup() couldn't connect");
             }
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index e1bbc08..bf5be95 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -547,11 +547,9 @@
      *        set can be restored.
      * @param transportID The name of the transport to use for the restore operation.
      *        May be null, in which case the current active transport is used.
-     * @param operationType Type of the operation, see {@link BackupManager#OperationType}
      * @return An interface to the restore session, or null on error.
      */
-    IRestoreSession beginRestoreSessionForUser(int userId, String packageName, String transportID,
-            int operationType);
+    IRestoreSession beginRestoreSessionForUser(int userId, String packageName, String transportID);
 
     /**
      * Notify the backup manager that a BackupAgent has completed the operation
@@ -680,7 +678,7 @@
      * {@link android.app.backup.IBackupManager.requestBackupForUser} for the calling user id.
      */
     int requestBackup(in String[] packages, IBackupObserver observer, IBackupManagerMonitor monitor,
-        int flags, int operationType);
+            int flags);
 
     /**
      * Cancel all running backups. After this call returns, no currently running backups will
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/core/java/android/app/time/ExternalTimeSuggestion.aidl
similarity index 94%
rename from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
rename to core/java/android/app/time/ExternalTimeSuggestion.aidl
index 14d57bf..07a0fbb 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/core/java/android/app/time/ExternalTimeSuggestion.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package android.app.time;
 
 parcelable ExternalTimeSuggestion;
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.java b/core/java/android/app/time/ExternalTimeSuggestion.java
similarity index 99%
rename from core/java/android/app/timedetector/ExternalTimeSuggestion.java
rename to core/java/android/app/time/ExternalTimeSuggestion.java
index 7ad303a..b566eab 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.java
+++ b/core/java/android/app/time/ExternalTimeSuggestion.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package android.app.time;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl
index 4626543..c4546be 100644
--- a/core/java/android/app/timedetector/ITimeDetectorService.aidl
+++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl
@@ -16,7 +16,7 @@
 
 package android.app.timedetector;
 
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
 import android.app.timedetector.GnssTimeSuggestion;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index df8d797..76f3785 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
+import android.app.time.ExternalTimeSuggestion;
 import android.content.Context;
 import android.os.SystemClock;
 import android.os.TimestampedValue;
diff --git a/core/java/android/app/timedetector/TimeDetectorImpl.java b/core/java/android/app/timedetector/TimeDetectorImpl.java
index f80869f..ef818ef 100644
--- a/core/java/android/app/timedetector/TimeDetectorImpl.java
+++ b/core/java/android/app/timedetector/TimeDetectorImpl.java
@@ -17,6 +17,7 @@
 package android.app.timedetector;
 
 import android.annotation.NonNull;
+import android.app.time.ExternalTimeSuggestion;
 import android.content.Context;
 import android.os.RemoteException;
 import android.os.ServiceManager;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2a402b2..025d777 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3479,6 +3479,7 @@
             STORAGE_STATS_SERVICE,
             WALLPAPER_SERVICE,
             TIME_ZONE_RULES_MANAGER_SERVICE,
+            VIBRATOR_MANAGER_SERVICE,
             VIBRATOR_SERVICE,
             //@hide: STATUS_BAR_SERVICE,
             CONNECTIVITY_SERVICE,
@@ -3625,9 +3626,11 @@
      *   (e.g., GPS) updates.
      *  <dt> {@link #SEARCH_SERVICE} ("search")
      *  <dd> A {@link android.app.SearchManager} for handling search.
+     *  <dt> {@link #VIBRATOR_MANAGER_SERVICE} ("vibrator_manager")
+     *  <dd> A {@link android.os.VibratorManager} for accessing the device vibrators, interacting
+     *  with individual ones and playing synchronized effects on multiple vibrators.
      *  <dt> {@link #VIBRATOR_SERVICE} ("vibrator")
-     *  <dd> A {@link android.os.Vibrator} for interacting with the vibrator
-     *  hardware.
+     *  <dd> A {@link android.os.Vibrator} for interacting with the vibrator hardware.
      *  <dt> {@link #CONNECTIVITY_SERVICE} ("connectivity")
      *  <dd> A {@link android.net.ConnectivityManager ConnectivityManager} for
      *  handling management of network connections.
@@ -3707,6 +3710,8 @@
      * @see android.hardware.SensorManager
      * @see #STORAGE_SERVICE
      * @see android.os.storage.StorageManager
+     * @see #VIBRATOR_MANAGER_SERVICE
+     * @see android.os.VibratorManager
      * @see #VIBRATOR_SERVICE
      * @see android.os.Vibrator
      * @see #CONNECTIVITY_SERVICE
@@ -4034,8 +4039,19 @@
     public static final String WALLPAPER_SERVICE = "wallpaper";
 
     /**
-     * Use with {@link #getSystemService(String)} to retrieve a {@link
-     * android.os.Vibrator} for interacting with the vibration hardware.
+     * Use with {@link #getSystemService(String)} to retrieve a {@link android.os.VibratorManager}
+     * for accessing the device vibrators, interacting with individual ones and playing synchronized
+     * effects on multiple vibrators.
+     *
+     * @see #getSystemService(String)
+     * @see android.os.VibratorManager
+     */
+    @SuppressLint("ServiceName")
+    public static final String VIBRATOR_MANAGER_SERVICE = "vibrator_manager";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a {@link android.os.Vibrator} for
+     * interacting with the vibration hardware.
      *
      * @see #getSystemService(String)
      * @see android.os.Vibrator
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 30b2404..4abd8cd 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5718,7 +5718,7 @@
     public static final String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT";
 
     /**
-     * Used in the extra field in the remote intent. It's astring token passed with the
+     * Used in the extra field in the remote intent. It's a string token passed with the
      * remote intent.
      */
     public static final String EXTRA_REMOTE_INTENT_TOKEN =
@@ -6062,6 +6062,16 @@
      */
     public static final String EXTRA_UNSTARTABLE_REASON = "android.intent.extra.UNSTARTABLE_REASON";
 
+    /**
+     * A boolean extra indicating whether an activity is bubbled. Set on the shortcut or
+     * pending intent provided for the bubble. If the extra is not present or false, then it is not
+     * bubbled.
+     *
+     * @see android.app.Notification.Builder#setBubbleMetadata(Notification.BubbleMetadata)
+     * @see android.app.Notification.BubbleMetadata.Builder#Builder(String)
+     */
+    public static final String EXTRA_IS_BUBBLED = "android.intent.extra.IS_BUBBLED";
+
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
     // Intent flags (see mFlags variable).
diff --git a/core/java/android/content/pm/DataLoaderManager.java b/core/java/android/content/pm/DataLoaderManager.java
index e8fb241..4d79936 100644
--- a/core/java/android/content/pm/DataLoaderManager.java
+++ b/core/java/android/content/pm/DataLoaderManager.java
@@ -41,6 +41,7 @@
      * @param dataLoaderId ID for the new data loader binder service.
      * @param params       DataLoaderParamsParcel object that contains data loader params, including
      *                     its package name, class name, and additional parameters.
+     * @param bindDelayMs  introduce a delay before actual bind in case we want to avoid busylooping
      * @param listener     Callback for the data loader service to report status back to the
      *                     caller.
      * @return false if 1) target ID collides with a data loader that is already bound to data
@@ -48,9 +49,9 @@
      * or 4) fails to bind to the specified data loader service, otherwise return true.
      */
     public boolean bindToDataLoader(int dataLoaderId, @NonNull DataLoaderParamsParcel params,
-            @NonNull IDataLoaderStatusListener listener) {
+            long bindDelayMs, @NonNull IDataLoaderStatusListener listener) {
         try {
-            return mService.bindToDataLoader(dataLoaderId, params, listener);
+            return mService.bindToDataLoader(dataLoaderId, params, bindDelayMs, listener);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/content/pm/IDataLoaderManager.aidl b/core/java/android/content/pm/IDataLoaderManager.aidl
index 93b3de7..dda4d36 100644
--- a/core/java/android/content/pm/IDataLoaderManager.aidl
+++ b/core/java/android/content/pm/IDataLoaderManager.aidl
@@ -23,7 +23,7 @@
 
 /** @hide */
 interface IDataLoaderManager {
-    boolean bindToDataLoader(int id, in DataLoaderParamsParcel params,
+    boolean bindToDataLoader(int id, in DataLoaderParamsParcel params, long bindDelayMs,
             IDataLoaderStatusListener listener);
     IDataLoader getDataLoader(int dataLoaderId);
     void unbindFromDataLoader(int dataLoaderId);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index a46876e..7fe2a41 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -808,4 +808,6 @@
 
     PackageManager.Property getProperty(String propertyName, String packageName, String className);
     ParceledListSlice queryProperty(String propertyName, int componentType);
+
+    void setKeepUninstalledPackages(in List<String> packageList);
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b95b991b..6cfcce3 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -47,8 +47,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
-import android.content.pm.verify.domain.DomainVerificationManager;
 import android.content.pm.dex.ArtManager;
+import android.content.pm.verify.domain.DomainVerificationManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
@@ -9282,4 +9282,20 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Set a list of apps to keep around as APKs even if no user has currently installed it.
+     * @param packageList List of package names to keep cached.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES)
+    @TestApi
+    public void setKeepUninstalledPackages(@NonNull List<String> packageList) {
+        try {
+            ActivityThread.getPackageManager().setKeepUninstalledPackages(packageList);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 0819d17..bf8d1f6 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -6151,6 +6151,56 @@
         }
 
         /**
+         * Returns whether this instance is currently signed, or has ever been signed, with a
+         * signing certificate from the provided {@link Set} of {@code certDigests}.
+         *
+         * <p>The provided {@code certDigests} should contain the SHA-256 digest of the DER encoding
+         * of each trusted certificate with the digest characters in upper case. If this instance
+         * has multiple signers then all signers must be in the provided {@code Set}. If this
+         * instance has a signing lineage then this method will return true if any of the previous
+         * signers in the lineage match one of the entries in the {@code Set}.
+         */
+        public boolean hasAncestorOrSelfWithDigest(Set<String> certDigests) {
+            if (this == UNKNOWN || certDigests == null || certDigests.size() == 0) {
+                return false;
+            }
+            // If an app is signed by multiple signers then all of the signers must be in the Set.
+            if (signatures.length > 1) {
+                // If the Set has less elements than the number of signatures then immediately
+                // return false as there's no way to satisfy the requirement of all signatures being
+                // in the Set.
+                if (certDigests.size() < signatures.length) {
+                    return false;
+                }
+                for (Signature signature : signatures) {
+                    String signatureDigest = PackageUtils.computeSha256Digest(
+                            signature.toByteArray());
+                    if (!certDigests.contains(signatureDigest)) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+
+            String signatureDigest = PackageUtils.computeSha256Digest(signatures[0].toByteArray());
+            if (certDigests.contains(signatureDigest)) {
+                return true;
+            }
+            if (hasPastSigningCertificates()) {
+                // The last element in the pastSigningCertificates array is the current signer;
+                // since that was verified above just check all the signers in the lineage.
+                for (int i = 0; i < pastSigningCertificates.length - 1; i++) {
+                    signatureDigest = PackageUtils.computeSha256Digest(
+                            pastSigningCertificates[i].toByteArray());
+                    if (certDigests.contains(signatureDigest)) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+
+        /**
          * Returns the SigningDetails with a descendant (or same) signer after verifying the
          * descendant has the same, a superset, or a subset of the lineage of the ancestor.
          *
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 35f02a8..a2e533a 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -30,6 +30,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
 
 /**
  * Information you can retrieve about a particular security permission
@@ -278,6 +279,15 @@
     @SystemApi
     public static final int PROTECTION_FLAG_ROLE = 0x4000000;
 
+    /**
+     * Additional flag for {@link #protectionLevel}, correspoinding to the {@code knownSigner} value
+     * of {@link android.R.attr#protectionLevel}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int PROTECTION_FLAG_KNOWN_SIGNER = 0x8000000;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "PROTECTION_FLAG_" }, value = {
             PROTECTION_FLAG_PRIVILEGED,
@@ -303,6 +313,7 @@
             PROTECTION_FLAG_RETAIL_DEMO,
             PROTECTION_FLAG_RECENTS,
             PROTECTION_FLAG_ROLE,
+            PROTECTION_FLAG_KNOWN_SIGNER,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ProtectionFlags {}
@@ -466,6 +477,15 @@
      */
     public @Nullable CharSequence nonLocalizedDescription;
 
+    /**
+     * A {@link Set} of trusted signing certificate digests. If this permission has the {@link
+     * #PROTECTION_FLAG_KNOWN_SIGNER} flag set the permission will be granted to a requesting app
+     * if the app is signed by any of these certificates.
+     *
+     * @hide
+     */
+    public @Nullable Set<String> knownCerts;
+
     /** @hide */
     public static int fixProtectionLevel(int level) {
         if (level == PROTECTION_SIGNATURE_OR_SYSTEM) {
@@ -570,6 +590,9 @@
         if ((level & PermissionInfo.PROTECTION_FLAG_ROLE) != 0) {
             protLevel.append("|role");
         }
+        if ((level & PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER) != 0) {
+            protLevel.append("|knownSigner");
+        }
         return protLevel.toString();
     }
 
diff --git a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
index fb0d904..9a84ded 100644
--- a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
@@ -655,6 +655,7 @@
         pi.protectionLevel = p.getProtectionLevel();
         pi.descriptionRes = p.getDescriptionRes();
         pi.flags = p.getFlags();
+        pi.knownCerts = p.getKnownCerts();
 
         if ((flags & PackageManager.GET_META_DATA) == 0) {
             return pi;
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermission.java b/core/java/android/content/pm/parsing/component/ParsedPermission.java
index f99a0b1..35bb33c 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermission.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermission.java
@@ -26,6 +26,8 @@
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
 
+import java.util.Set;
+
 /** @hide */
 public class ParsedPermission extends ParsedComponent {
 
@@ -39,6 +41,8 @@
     boolean tree;
     @Nullable
     private ParsedPermissionGroup parsedPermissionGroup;
+    @Nullable
+    Set<String> knownCerts;
 
     @VisibleForTesting
     public ParsedPermission() {
@@ -81,6 +85,10 @@
         return protectionLevel & ~PermissionInfo.PROTECTION_MASK_BASE;
     }
 
+    public @Nullable Set<String> getKnownCerts() {
+        return knownCerts;
+    }
+
     public int calculateFootprint() {
         int size = getName().length();
         if (getNonLocalizedLabel() != null) {
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
index 9012b5ce..a7cecbe 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
@@ -25,6 +25,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.internal.R;
@@ -32,6 +33,8 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.util.Locale;
+import java.util.Set;
 
 /** @hide */
 public class ParsedPermissionUtils {
@@ -90,6 +93,43 @@
             permission.flags = sa.getInt(
                     R.styleable.AndroidManifestPermission_permissionFlags, 0);
 
+            final int knownCertsResource = sa.getResourceId(
+                    R.styleable.AndroidManifestPermission_knownCerts, 0);
+            if (knownCertsResource != 0) {
+                // The knownCerts attribute supports both a string array resource as well as a
+                // string resource for the case where the permission should only be granted to a
+                // single known signer.
+                final String resourceType = res.getResourceTypeName(knownCertsResource);
+                if (resourceType.equals("array")) {
+                    final String[] knownCerts = res.getStringArray(knownCertsResource);
+                    if (knownCerts != null) {
+                        // Convert the provided digest to upper case for consistent Set membership
+                        // checks when verifying the signing certificate digests of requesting apps.
+                        permission.knownCerts = new ArraySet<>();
+                        for (String knownCert : knownCerts) {
+                            permission.knownCerts.add(knownCert.toUpperCase(Locale.US));
+                        }
+                    }
+                } else {
+                    final String knownCert = res.getString(knownCertsResource);
+                    if (knownCert != null) {
+                        permission.knownCerts = Set.of(knownCert.toUpperCase(Locale.US));
+                    }
+                }
+                if (permission.knownCerts == null) {
+                    Slog.w(TAG, packageName + " defines a knownSigner permission but"
+                            + " the provided knownCerts resource is null");
+                }
+            } else {
+                // If the knownCerts resource ID is null check if the app specified a string
+                // value for the attribute representing a single trusted signer.
+                final String knownCert = sa.getString(
+                        R.styleable.AndroidManifestPermission_knownCerts);
+                if (knownCert != null) {
+                    permission.knownCerts = Set.of(knownCert.toUpperCase(Locale.US));
+                }
+            }
+
             // For now only platform runtime permissions can be restricted
             if (!permission.isRuntime() || !"android".equals(permission.getPackageName())) {
                 permission.flags &= ~PermissionInfo.FLAG_HARD_RESTRICTED;
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index abf694f..bbde8b1 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -252,9 +252,10 @@
             }
 
             if (overrideScale != 1.0f) {
-                applicationDensity = DisplayMetrics.DENSITY_DEFAULT;
                 applicationScale = overrideScale;
                 applicationInvertedScale = 1.0f / overrideScale;
+                applicationDensity = (int) ((DisplayMetrics.DENSITY_DEVICE_STABLE
+                        * applicationInvertedScale) + .5f);
                 compatFlags |= HAS_OVERRIDE_SCALING;
             } else if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
                 applicationDensity = DisplayMetrics.DENSITY_DEVICE;
@@ -519,10 +520,6 @@
         if (isScalingRequired()) {
             float invertedRatio = applicationInvertedScale;
             inoutConfig.densityDpi = (int)((inoutConfig.densityDpi * invertedRatio) + .5f);
-            inoutConfig.screenWidthDp = (int) ((inoutConfig.screenWidthDp * invertedRatio) + .5f);
-            inoutConfig.screenHeightDp = (int) ((inoutConfig.screenHeightDp * invertedRatio) + .5f);
-            inoutConfig.smallestScreenWidthDp =
-                    (int) ((inoutConfig.smallestScreenWidthDp * invertedRatio) + .5f);
             inoutConfig.windowConfiguration.getMaxBounds().scale(invertedRatio);
             inoutConfig.windowConfiguration.getBounds().scale(invertedRatio);
             final Rect appBounds = inoutConfig.windowConfiguration.getAppBounds();
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 08b1e24..5f5697a 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -236,7 +236,7 @@
     @RequiresPermission(TEST_BIOMETRIC)
     public BiometricTestSession createTestSession(int sensorId) {
         try {
-            return new BiometricTestSession(mContext,
+            return new BiometricTestSession(mContext, sensorId,
                     mService.createTestSession(sensorId, mContext.getOpPackageName()));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
diff --git a/core/java/android/hardware/biometrics/BiometricTestSession.java b/core/java/android/hardware/biometrics/BiometricTestSession.java
index 2b68989..1c35608 100644
--- a/core/java/android/hardware/biometrics/BiometricTestSession.java
+++ b/core/java/android/hardware/biometrics/BiometricTestSession.java
@@ -25,6 +25,7 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.RemoteException;
 import android.util.ArraySet;
+import android.util.Log;
 
 /**
  * Common set of interfaces to test biometric-related APIs, including {@link BiometricPrompt} and
@@ -33,7 +34,10 @@
  */
 @TestApi
 public class BiometricTestSession implements AutoCloseable {
+    private static final String TAG = "BiometricTestSession";
+
     private final Context mContext;
+    private final int mSensorId;
     private final ITestSession mTestSession;
 
     // Keep track of users that were tested, which need to be cleaned up when finishing.
@@ -42,8 +46,10 @@
     /**
      * @hide
      */
-    public BiometricTestSession(@NonNull Context context, @NonNull ITestSession testSession) {
+    public BiometricTestSession(@NonNull Context context, int sensorId,
+            @NonNull ITestSession testSession) {
         mContext = context;
+        mSensorId = sensorId;
         mTestSession = testSession;
         mTestedUsers = new ArraySet<>();
         setTestHalEnabled(true);
@@ -61,6 +67,7 @@
     @RequiresPermission(TEST_BIOMETRIC)
     private void setTestHalEnabled(boolean enabled) {
         try {
+            Log.w(TAG, "setTestHalEnabled, sensor: " + mSensorId + " enabled: " + enabled);
             mTestSession.setTestHalEnabled(enabled);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 188a2a4..a614ebf 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -153,7 +153,7 @@
     @RequiresPermission(TEST_BIOMETRIC)
     public BiometricTestSession createTestSession(int sensorId) {
         try {
-            return new BiometricTestSession(mContext,
+            return new BiometricTestSession(mContext, sensorId,
                     mService.createTestSession(sensorId, mContext.getOpPackageName()));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
diff --git a/core/java/android/hardware/input/InputDeviceVibrator.java b/core/java/android/hardware/input/InputDeviceVibrator.java
index f4d8a65..a4817ae 100644
--- a/core/java/android/hardware/input/InputDeviceVibrator.java
+++ b/core/java/android/hardware/input/InputDeviceVibrator.java
@@ -74,6 +74,11 @@
     }
 
     @Override
+    public int getId() {
+        return mVibratorId;
+    }
+
+    @Override
     public boolean hasVibrator() {
         return true;
     }
diff --git a/core/java/android/hardware/input/InputDeviceVibratorManager.java b/core/java/android/hardware/input/InputDeviceVibratorManager.java
index a381b02..d843407 100644
--- a/core/java/android/hardware/input/InputDeviceVibratorManager.java
+++ b/core/java/android/hardware/input/InputDeviceVibratorManager.java
@@ -16,9 +16,12 @@
 
 package android.hardware.input;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Binder;
 import android.os.CombinedVibrationEffect;
 import android.os.NullVibrator;
+import android.os.VibrationAttributes;
 import android.os.Vibrator;
 import android.os.VibratorManager;
 import android.util.SparseArray;
@@ -86,6 +89,7 @@
         }
     }
 
+    @NonNull
     @Override
     public int[] getVibratorIds() {
         synchronized (mVibrators) {
@@ -97,6 +101,7 @@
         }
     }
 
+    @NonNull
     @Override
     public Vibrator getVibrator(int vibratorId) {
         synchronized (mVibrators) {
@@ -107,6 +112,7 @@
         return NullVibrator.getInstance();
     }
 
+    @NonNull
     @Override
     public Vibrator getDefaultVibrator() {
         // Returns vibrator ID 0
@@ -119,7 +125,13 @@
     }
 
     @Override
-    public void vibrate(CombinedVibrationEffect effect) {
+    public void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect,
+            String reason, @Nullable VibrationAttributes attributes) {
         mInputManager.vibrate(mDeviceId, effect, mToken);
     }
+
+    @Override
+    public void cancel() {
+        mInputManager.cancelVibrate(mDeviceId, mToken);
+    }
 }
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index cc86a60..6f89254 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -994,6 +994,19 @@
          */
         public abstract long getScreenOnEnergy();
 
+        /**
+         * Returns the energies used by this uid for each
+         * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer
+         * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
+         *
+         * @return energies (in microjoules) used since boot for each (custom) energy consumer of
+         *         type OTHER, indexed by their ordinal. Returns null if no energy reporting is
+         *         supported.
+         *
+         * {@hide}
+         */
+        public abstract @Nullable long[] getCustomMeasuredEnergiesMicroJoules();
+
         public static abstract class Sensor {
 
             @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
@@ -2511,6 +2524,19 @@
      */
     public abstract long getScreenDozeEnergy();
 
+    /**
+     * Returns the energies used for each
+     * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer
+     * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
+     *
+     * @return energies (in microjoules) used since boot for each (custom) energy consumer of
+     *         type OTHER, indexed by their ordinal. Returns null if no energy reporting is
+     *         supported.
+     *
+     * {@hide}
+     */
+    public abstract @Nullable long[] getCustomMeasuredEnergiesMicroJoules();
+
     public static final BitDescription[] HISTORY_STATE_DESCRIPTIONS = new BitDescription[] {
         new BitDescription(HistoryItem.STATE_CPU_RUNNING_FLAG, "running", "r"),
         new BitDescription(HistoryItem.STATE_WAKE_LOCK_FLAG, "wake_lock", "w"),
diff --git a/core/java/android/os/CombinedVibrationEffect.java b/core/java/android/os/CombinedVibrationEffect.java
index cb4e9cb..c8e682c 100644
--- a/core/java/android/os/CombinedVibrationEffect.java
+++ b/core/java/android/os/CombinedVibrationEffect.java
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.annotation.NonNull;
+import android.annotation.TestApi;
 import android.util.SparseArray;
 
 import com.android.internal.util.Preconditions;
@@ -86,7 +87,20 @@
         return 0;
     }
 
-    /** @hide */
+    /**
+     * Gets the estimated duration of the combined vibration in milliseconds.
+     *
+     * <p>For synced combinations this means the maximum duration of any individual {@link
+     * VibrationEffect}. For sequential combinations, this is a sum of each step and delays.
+     *
+     * <p>For combinations of effects without a defined end (e.g. a Waveform with a non-negative
+     * repeat index), this returns Long.MAX_VALUE. For effects with an unknown duration (e.g.
+     * Prebaked effects where the length is device and potentially run-time dependent), this returns
+     * -1.
+     *
+     * @hide
+     */
+    @TestApi
     public abstract long getDuration();
 
     /** @hide */
@@ -256,6 +270,7 @@
      *
      * @hide
      */
+    @TestApi
     public static final class Mono extends CombinedVibrationEffect {
         private final VibrationEffect mEffect;
 
@@ -267,6 +282,7 @@
             mEffect = effect;
         }
 
+        @NonNull
         public VibrationEffect getEffect() {
             return mEffect;
         }
@@ -282,6 +298,7 @@
             mEffect.validate();
         }
 
+        /** @hide */
         @Override
         public boolean hasVibrator(int vibratorId) {
             return true;
@@ -307,7 +324,12 @@
         }
 
         @Override
-        public void writeToParcel(Parcel out, int flags) {
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel out, int flags) {
             out.writeInt(PARCEL_TOKEN_MONO);
             mEffect.writeToParcel(out, flags);
         }
@@ -335,6 +357,7 @@
      *
      * @hide
      */
+    @TestApi
     public static final class Stereo extends CombinedVibrationEffect {
 
         /** Mapping vibrator ids to effects. */
@@ -357,6 +380,7 @@
         }
 
         /** Effects to be performed in sync, where each key represents the vibrator id. */
+        @NonNull
         public SparseArray<VibrationEffect> getEffects() {
             return mEffects;
         }
@@ -394,6 +418,7 @@
             }
         }
 
+        /** @hide */
         @Override
         public boolean hasVibrator(int vibratorId) {
             return mEffects.indexOfKey(vibratorId) >= 0;
@@ -418,7 +443,7 @@
 
         @Override
         public int hashCode() {
-            return Objects.hash(mEffects);
+            return mEffects.contentHashCode();
         }
 
         @Override
@@ -427,7 +452,12 @@
         }
 
         @Override
-        public void writeToParcel(Parcel out, int flags) {
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel out, int flags) {
             out.writeInt(PARCEL_TOKEN_STEREO);
             out.writeInt(mEffects.size());
             for (int i = 0; i < mEffects.size(); i++) {
@@ -459,6 +489,7 @@
      *
      * @hide
      */
+    @TestApi
     public static final class Sequential extends CombinedVibrationEffect {
         private final List<CombinedVibrationEffect> mEffects;
         private final List<Integer> mDelays;
@@ -480,11 +511,13 @@
         }
 
         /** Effects to be performed in sequence. */
+        @NonNull
         public List<CombinedVibrationEffect> getEffects() {
             return mEffects;
         }
 
         /** Delay to be applied before each effect in {@link #getEffects()}. */
+        @NonNull
         public List<Integer> getDelays() {
             return mDelays;
         }
@@ -542,6 +575,7 @@
             }
         }
 
+        /** @hide */
         @Override
         public boolean hasVibrator(int vibratorId) {
             final int effectCount = mEffects.size();
@@ -564,7 +598,7 @@
 
         @Override
         public int hashCode() {
-            return Objects.hash(mEffects);
+            return Objects.hash(mEffects, mDelays);
         }
 
         @Override
@@ -573,7 +607,12 @@
         }
 
         @Override
-        public void writeToParcel(Parcel out, int flags) {
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel out, int flags) {
             out.writeInt(PARCEL_TOKEN_SEQUENTIAL);
             out.writeInt(mEffects.size());
             for (int i = 0; i < mEffects.size(); i++) {
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 6295124..124c0b0 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -1359,8 +1359,17 @@
         }
 
         final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
-        return appOps.checkOpNoThrow(AppOpsManager.OP_LEGACY_STORAGE,
-                uid, context.getOpPackageName()) == AppOpsManager.MODE_ALLOWED;
+        final String opPackageName = context.getOpPackageName();
+
+        if (appOps.noteOpNoThrow(AppOpsManager.OP_LEGACY_STORAGE, uid,
+                opPackageName) == AppOpsManager.MODE_ALLOWED) {
+            return true;
+        }
+
+        // Legacy external storage access is granted to instrumentations invoked with
+        // "--no-isolated-storage" flag.
+        return appOps.noteOpNoThrow(AppOpsManager.OP_NO_ISOLATED_STORAGE, uid,
+                opPackageName) == AppOpsManager.MODE_ALLOWED;
     }
 
     private static boolean isScopedStorageEnforced(boolean defaultScopedStorage,
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
deleted file mode 100644
index 1cd48dc..0000000
--- a/core/java/android/os/IVibratorService.aidl
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * Copyright (c) 2007, 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.os;
-
-import android.os.VibrationEffect;
-import android.os.VibrationAttributes;
-import android.os.VibratorInfo;
-import android.os.IVibratorStateListener;
-
-/** {@hide} */
-interface IVibratorService
-{
-    boolean hasVibrator();
-    boolean isVibrating();
-    VibratorInfo getVibratorInfo();
-    boolean registerVibratorStateListener(in IVibratorStateListener listener);
-    boolean unregisterVibratorStateListener(in IVibratorStateListener listener);
-    boolean hasAmplitudeControl();
-    void vibrate(int uid, String opPkg, in VibrationEffect effect,
-            in VibrationAttributes attributes, String reason, IBinder token);
-    void cancelVibrate(IBinder token);
-}
-
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 2559a33..dac1ede 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -4,7 +4,6 @@
 per-file IExternalVibrationController.aidl = michaelwr@google.com
 per-file IExternalVibratorService.aidl = michaelwr@google.com
 per-file IVibratorManagerService.aidl = michaelwr@google.com
-per-file IVibratorService.aidl = michaelwr@google.com
 per-file NullVibrator.java = michaelwr@google.com
 per-file SystemVibrator.java = michaelwr@google.com
 per-file VibrationEffect.aidl = michaelwr@google.com
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 30afe38..b42a495 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -17,19 +17,18 @@
 package android.os;
 
 import android.annotation.CallbackExecutor;
-import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.media.AudioAttributes;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -41,142 +40,46 @@
 public class SystemVibrator extends Vibrator {
     private static final String TAG = "Vibrator";
 
-    private static final int VIBRATOR_PRESENT_UNKNOWN = 0;
-    private static final int VIBRATOR_PRESENT_YES = 1;
-    private static final int VIBRATOR_PRESENT_NO = 2;
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({VIBRATOR_PRESENT_UNKNOWN, VIBRATOR_PRESENT_YES, VIBRATOR_PRESENT_NO})
-    private @interface VibratorPresent {}
-
-    private final IVibratorService mService;
-    private final IVibratorManagerService mManagerService;
-    private final Object mLock = new Object();
-    private final Binder mToken = new Binder();
+    private final VibratorManager mVibratorManager;
     private final Context mContext;
-    @GuardedBy("mLock")
-    private VibratorInfo mVibratorInfo;
-    @GuardedBy("mLock")
-    @VibratorPresent
-    private int mVibratorPresent;
 
-    @GuardedBy("mDelegates")
-    private final ArrayMap<OnVibratorStateChangedListener,
-            OnVibratorStateChangedListenerDelegate> mDelegates = new ArrayMap<>();
+    @GuardedBy("mBrokenListeners")
+    private final ArrayList<AllVibratorsStateListener> mBrokenListeners = new ArrayList<>();
 
-    @UnsupportedAppUsage
-    public SystemVibrator() {
-        mContext = null;
-        mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
-        mManagerService = IVibratorManagerService.Stub.asInterface(
-                ServiceManager.getService("vibrator_manager"));
-    }
+    @GuardedBy("mRegisteredListeners")
+    private final ArrayMap<OnVibratorStateChangedListener, AllVibratorsStateListener>
+            mRegisteredListeners = new ArrayMap<>();
 
     @UnsupportedAppUsage
     public SystemVibrator(Context context) {
         super(context);
         mContext = context;
-        mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
-        mManagerService = IVibratorManagerService.Stub.asInterface(
-                ServiceManager.getService("vibrator_manager"));
+        mVibratorManager = mContext.getSystemService(VibratorManager.class);
     }
 
     @Override
     public boolean hasVibrator() {
-        try {
-            synchronized (mLock) {
-                if (mVibratorPresent == VIBRATOR_PRESENT_UNKNOWN && mService != null) {
-                    mVibratorPresent =
-                            mService.hasVibrator() ? VIBRATOR_PRESENT_YES : VIBRATOR_PRESENT_NO;
-                }
-                return mVibratorPresent == VIBRATOR_PRESENT_YES;
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to query vibrator presence", e);
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to check if vibrator exists; no vibrator manager.");
             return false;
         }
+        return mVibratorManager.getVibratorIds().length > 0;
     }
 
-    /**
-     * Check whether the vibrator is vibrating.
-     *
-     * @return True if the hardware is vibrating, otherwise false.
-     */
     @Override
     public boolean isVibrating() {
-        if (mService == null) {
-            Log.w(TAG, "Failed to vibrate; no vibrator service.");
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to vibrate; no vibrator manager.");
             return false;
         }
-        try {
-            return mService.isVibrating();
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
+        for (int vibratorId : mVibratorManager.getVibratorIds()) {
+            if (mVibratorManager.getVibrator(vibratorId).isVibrating()) {
+                return true;
+            }
         }
         return false;
     }
 
-    private class OnVibratorStateChangedListenerDelegate extends
-            IVibratorStateListener.Stub {
-        private final Executor mExecutor;
-        private final OnVibratorStateChangedListener mListener;
-
-        OnVibratorStateChangedListenerDelegate(@NonNull OnVibratorStateChangedListener listener,
-                @NonNull Executor executor) {
-            mExecutor = executor;
-            mListener = listener;
-        }
-
-        @Override
-        public void onVibrating(boolean isVibrating) {
-            mExecutor.execute(() -> mListener.onVibratorStateChanged(isVibrating));
-        }
-    }
-
-    /**
-     * Adds a listener for vibrator state change. If the listener was previously added and not
-     * removed, this call will be ignored.
-     *
-     * @param listener Listener to be added.
-     * @param executor The {@link Executor} on which the listener's callbacks will be executed on.
-     */
-    @Override
-    public void addVibratorStateListener(
-            @NonNull @CallbackExecutor Executor executor,
-            @NonNull OnVibratorStateChangedListener listener) {
-        Objects.requireNonNull(listener);
-        Objects.requireNonNull(executor);
-        if (mService == null) {
-            Log.w(TAG, "Failed to add vibrate state listener; no vibrator service.");
-            return;
-        }
-
-        synchronized (mDelegates) {
-            // If listener is already registered, reject and return.
-            if (mDelegates.containsKey(listener)) {
-                Log.w(TAG, "Listener already registered.");
-                return;
-            }
-            try {
-                final OnVibratorStateChangedListenerDelegate delegate =
-                        new OnVibratorStateChangedListenerDelegate(listener, executor);
-                if (!mService.registerVibratorStateListener(delegate)) {
-                    Log.w(TAG, "Failed to register vibrate state listener");
-                    return;
-                }
-                mDelegates.put(listener, delegate);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
-    }
-
-    /**
-     * Adds a listener for vibrator state changes. Callbacks will be executed on the main thread.
-     * If the listener was previously added and not removed, this call will be ignored.
-     *
-     * @param listener listener to be added
-     */
     @Override
     public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
         Objects.requireNonNull(listener);
@@ -187,88 +90,128 @@
         addVibratorStateListener(mContext.getMainExecutor(), listener);
     }
 
-    /**
-     * Removes the listener for vibrator state changes. If the listener was not previously
-     * registered, this call will do nothing.
-     *
-     * @param listener Listener to be removed.
-     */
     @Override
-    public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+    public void addVibratorStateListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnVibratorStateChangedListener listener) {
         Objects.requireNonNull(listener);
-        if (mService == null) {
-            Log.w(TAG, "Failed to remove vibrate state listener; no vibrator service.");
+        Objects.requireNonNull(executor);
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to add vibrate state listener; no vibrator manager.");
             return;
         }
-        synchronized (mDelegates) {
-            // Check if the listener is registered, otherwise will return.
-            if (mDelegates.containsKey(listener)) {
-                final OnVibratorStateChangedListenerDelegate delegate = mDelegates.get(listener);
-                try {
-                    if (!mService.unregisterVibratorStateListener(delegate)) {
-                        Log.w(TAG, "Failed to unregister vibrate state listener");
-                        return;
-                    }
-                    mDelegates.remove(listener);
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
+        AllVibratorsStateListener delegate = null;
+        try {
+            synchronized (mRegisteredListeners) {
+                // If listener is already registered, reject and return.
+                if (mRegisteredListeners.containsKey(listener)) {
+                    Log.w(TAG, "Listener already registered.");
+                    return;
+                }
+                delegate = new AllVibratorsStateListener(executor, listener);
+                delegate.register(mVibratorManager);
+                mRegisteredListeners.put(listener, delegate);
+                delegate = null;
+            }
+        } finally {
+            if (delegate != null && delegate.hasRegisteredListeners()) {
+                // The delegate listener was left in a partial state with listeners registered to
+                // some but not all vibrators. Keep track of this to try to unregister them later.
+                synchronized (mBrokenListeners) {
+                    mBrokenListeners.add(delegate);
                 }
             }
+            tryUnregisterBrokenListeners();
         }
     }
 
     @Override
+    public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+        Objects.requireNonNull(listener);
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to remove vibrate state listener; no vibrator manager.");
+            return;
+        }
+        synchronized (mRegisteredListeners) {
+            if (mRegisteredListeners.containsKey(listener)) {
+                AllVibratorsStateListener delegate = mRegisteredListeners.get(listener);
+                delegate.unregister(mVibratorManager);
+                mRegisteredListeners.remove(listener);
+            }
+        }
+        tryUnregisterBrokenListeners();
+    }
+
+    @Override
     public boolean hasAmplitudeControl() {
-        if (mService == null) {
-            Log.w(TAG, "Failed to check amplitude control; no vibrator service.");
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to check vibrator has amplitude control; no vibrator manager.");
             return false;
         }
-        try {
-            return mService.hasAmplitudeControl();
-        } catch (RemoteException e) {
+        int[] vibratorIds = mVibratorManager.getVibratorIds();
+        if (vibratorIds.length == 0) {
+            return false;
         }
-        return false;
+        for (int vibratorId : vibratorIds) {
+            if (!mVibratorManager.getVibrator(vibratorId).hasAmplitudeControl()) {
+                return false;
+            }
+        }
+        return true;
     }
 
     @Override
     public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect,
             AudioAttributes attributes) {
-        if (mManagerService == null) {
-            Log.w(TAG, "Failed to set always-on effect; no vibrator service.");
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to set always-on effect; no vibrator manager.");
             return false;
         }
-        try {
-            VibrationAttributes atr = new VibrationAttributes.Builder(attributes, effect).build();
-            CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect);
-            return mManagerService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combinedEffect, atr);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to set always-on effect.", e);
-        }
-        return false;
+        VibrationAttributes attr = new VibrationAttributes.Builder(attributes, effect).build();
+        CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect);
+        return mVibratorManager.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combinedEffect, attr);
     }
 
     @Override
     public void vibrate(int uid, String opPkg, @NonNull VibrationEffect effect,
             String reason, @NonNull VibrationAttributes attributes) {
-        if (mService == null) {
-            Log.w(TAG, "Failed to vibrate; no vibrator service.");
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to vibrate; no vibrator manager.");
             return;
         }
-        try {
-            mService.vibrate(uid, opPkg, effect, attributes, reason, mToken);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to vibrate.", e);
-        }
+        CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect);
+        mVibratorManager.vibrate(uid, opPkg, combinedEffect, reason, attributes);
     }
 
     @Override
     public int[] areEffectsSupported(@VibrationEffect.EffectType int... effectIds) {
-        VibratorInfo vibratorInfo = getVibratorInfo();
         int[] supported = new int[effectIds.length];
-        for (int i = 0; i < effectIds.length; i++) {
-            supported[i] = vibratorInfo == null
-                    ? Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN
-                    : vibratorInfo.isEffectSupported(effectIds[i]);
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to check supported effects; no vibrator manager.");
+            Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_NO);
+            return supported;
+        }
+        int[] vibratorIds = mVibratorManager.getVibratorIds();
+        if (vibratorIds.length == 0) {
+            Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_NO);
+            return supported;
+        }
+        int[][] vibratorSupportMap = new int[vibratorIds.length][effectIds.length];
+        for (int i = 0; i < vibratorIds.length; i++) {
+            vibratorSupportMap[i] = mVibratorManager.getVibrator(
+                    vibratorIds[i]).areEffectsSupported(effectIds);
+        }
+        Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_YES);
+        for (int effectIdx = 0; effectIdx < effectIds.length; effectIdx++) {
+            for (int vibratorIdx = 0; vibratorIdx < vibratorIds.length; vibratorIdx++) {
+                int effectSupported = vibratorSupportMap[vibratorIdx][effectIdx];
+                if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_NO) {
+                    supported[effectIdx] = Vibrator.VIBRATION_EFFECT_SUPPORT_NO;
+                    break;
+                } else if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN) {
+                    supported[effectIdx] = Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN;
+                }
+            }
         }
         return supported;
     }
@@ -276,42 +219,169 @@
     @Override
     public boolean[] arePrimitivesSupported(
             @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
-        VibratorInfo vibratorInfo = getVibratorInfo();
         boolean[] supported = new boolean[primitiveIds.length];
-        for (int i = 0; i < primitiveIds.length; i++) {
-            supported[i] = vibratorInfo == null
-                    ? false : vibratorInfo.isPrimitiveSupported(primitiveIds[i]);
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to check supported primitives; no vibrator manager.");
+            Arrays.fill(supported, false);
+            return supported;
+        }
+        int[] vibratorIds = mVibratorManager.getVibratorIds();
+        if (vibratorIds.length == 0) {
+            Arrays.fill(supported, false);
+            return supported;
+        }
+        boolean[][] vibratorSupportMap = new boolean[vibratorIds.length][primitiveIds.length];
+        for (int i = 0; i < vibratorIds.length; i++) {
+            vibratorSupportMap[i] = mVibratorManager.getVibrator(
+                    vibratorIds[i]).arePrimitivesSupported(primitiveIds);
+        }
+        Arrays.fill(supported, true);
+        for (int primitiveIdx = 0; primitiveIdx < primitiveIds.length; primitiveIdx++) {
+            for (int vibratorIdx = 0; vibratorIdx < vibratorIds.length; vibratorIdx++) {
+                if (!vibratorSupportMap[vibratorIdx][primitiveIdx]) {
+                    supported[primitiveIdx] = false;
+                    break;
+                }
+            }
         }
         return supported;
     }
 
     @Override
     public void cancel() {
-        if (mService == null) {
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to cancel vibrate; no vibrator manager.");
             return;
         }
-        try {
-            mService.cancelVibrate(mToken);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to cancel vibration.", e);
+        mVibratorManager.cancel();
+    }
+
+    /**
+     * Tries to unregister individual {@link android.os.Vibrator.OnVibratorStateChangedListener}
+     * that were left registered to vibrators after failures to register them to all vibrators.
+     *
+     * <p>This might happen if {@link AllVibratorsStateListener} fails to register to any vibrator
+     * and also fails to unregister any previously registered single listeners to other vibrators.
+     *
+     * <p>This method never throws {@link RuntimeException} if it fails to unregister again, it will
+     * fail silently and attempt to unregister the same broken listener later.
+     */
+    private void tryUnregisterBrokenListeners() {
+        synchronized (mBrokenListeners) {
+            try {
+                for (int i = mBrokenListeners.size(); --i >= 0; ) {
+                    mBrokenListeners.get(i).unregister(mVibratorManager);
+                    mBrokenListeners.remove(i);
+                }
+            } catch (RuntimeException e) {
+                Log.w(TAG, "Failed to unregister broken listener", e);
+            }
         }
     }
 
-    @Nullable
-    private VibratorInfo getVibratorInfo() {
-        try {
+    /** Listener for a single vibrator state change. */
+    private static class SingleVibratorStateListener implements OnVibratorStateChangedListener {
+        private final AllVibratorsStateListener mAllVibratorsListener;
+        private final int mVibratorIdx;
+
+        SingleVibratorStateListener(AllVibratorsStateListener listener, int vibratorIdx) {
+            mAllVibratorsListener = listener;
+            mVibratorIdx = vibratorIdx;
+        }
+
+        @Override
+        public void onVibratorStateChanged(boolean isVibrating) {
+            mAllVibratorsListener.onVibrating(mVibratorIdx, isVibrating);
+        }
+    }
+
+    /** Listener for all vibrators state change. */
+    private static class AllVibratorsStateListener {
+        private final Object mLock = new Object();
+        private final Executor mExecutor;
+        private final OnVibratorStateChangedListener mDelegate;
+
+        @GuardedBy("mLock")
+        private final SparseArray<SingleVibratorStateListener> mVibratorListeners =
+                new SparseArray<>();
+
+        @GuardedBy("mLock")
+        private int mInitializedMask;
+        @GuardedBy("mLock")
+        private int mVibratingMask;
+
+        AllVibratorsStateListener(@NonNull Executor executor,
+                @NonNull OnVibratorStateChangedListener listener) {
+            mExecutor = executor;
+            mDelegate = listener;
+        }
+
+        boolean hasRegisteredListeners() {
             synchronized (mLock) {
-                if (mVibratorInfo != null) {
-                    return mVibratorInfo;
-                }
-                if (mService == null) {
-                    return null;
-                }
-                return mVibratorInfo = mService.getVibratorInfo();
+                return mVibratorListeners.size() > 0;
             }
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to query vibrator info");
-            throw e.rethrowFromSystemServer();
+        }
+
+        void register(VibratorManager vibratorManager) {
+            int[] vibratorIds = vibratorManager.getVibratorIds();
+            synchronized (mLock) {
+                for (int i = 0; i < vibratorIds.length; i++) {
+                    int vibratorId = vibratorIds[i];
+                    SingleVibratorStateListener listener = new SingleVibratorStateListener(this, i);
+                    try {
+                        vibratorManager.getVibrator(vibratorId).addVibratorStateListener(mExecutor,
+                                listener);
+                        mVibratorListeners.put(vibratorId, listener);
+                    } catch (RuntimeException e) {
+                        try {
+                            unregister(vibratorManager);
+                        } catch (RuntimeException e1) {
+                            Log.w(TAG,
+                                    "Failed to unregister listener while recovering from a failed "
+                                            + "register call", e1);
+                        }
+                        throw e;
+                    }
+                }
+            }
+        }
+
+        void unregister(VibratorManager vibratorManager) {
+            synchronized (mLock) {
+                for (int i = mVibratorListeners.size(); --i >= 0; ) {
+                    int vibratorId = mVibratorListeners.keyAt(i);
+                    SingleVibratorStateListener listener = mVibratorListeners.valueAt(i);
+                    vibratorManager.getVibrator(vibratorId).removeVibratorStateListener(listener);
+                    mVibratorListeners.removeAt(i);
+                }
+            }
+        }
+
+        void onVibrating(int vibratorIdx, boolean vibrating) {
+            mExecutor.execute(() -> {
+                boolean anyVibrating;
+                synchronized (mLock) {
+                    int allInitializedMask = 1 << mVibratorListeners.size() - 1;
+                    int vibratorMask = 1 << vibratorIdx;
+                    if ((mInitializedMask & vibratorMask) == 0) {
+                        // First state report for this vibrator, set vibrating initial value.
+                        mInitializedMask |= vibratorMask;
+                        mVibratingMask |= vibrating ? vibratorMask : 0;
+                    } else {
+                        // Flip vibrating value, if changed.
+                        boolean prevVibrating = (mVibratingMask & vibratorMask) != 0;
+                        if (prevVibrating != vibrating) {
+                            mVibratingMask ^= vibratorMask;
+                        }
+                    }
+                    if (mInitializedMask != allInitializedMask) {
+                        // Wait for all vibrators initial state to be reported before delegating.
+                        return;
+                    }
+                    anyVibrating = mVibratingMask != 0;
+                }
+                mDelegate.onVibratorStateChanged(anyVibrating);
+            });
         }
     }
 }
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
new file mode 100644
index 0000000..b528eb1
--- /dev/null
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -0,0 +1,360 @@
+/*
+ * 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 android.os;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * VibratorManager implementation that controls the system vibrators.
+ *
+ * @hide
+ */
+public class SystemVibratorManager extends VibratorManager {
+    private static final String TAG = "VibratorManager";
+
+    private final IVibratorManagerService mService;
+    private final Context mContext;
+    private final Binder mToken = new Binder();
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private int[] mVibratorIds;
+    @GuardedBy("mLock")
+    private final SparseArray<Vibrator> mVibrators = new SparseArray<>();
+
+    @GuardedBy("mLock")
+    private final ArrayMap<Vibrator.OnVibratorStateChangedListener,
+            OnVibratorStateChangedListenerDelegate> mListeners = new ArrayMap<>();
+
+    /**
+     * @hide to prevent subclassing from outside of the framework
+     */
+    public SystemVibratorManager(Context context) {
+        super(context);
+        mContext = context;
+        mService = IVibratorManagerService.Stub.asInterface(
+                ServiceManager.getService(Context.VIBRATOR_MANAGER_SERVICE));
+    }
+
+    @NonNull
+    @Override
+    public int[] getVibratorIds() {
+        synchronized (mLock) {
+            if (mVibratorIds != null) {
+                return mVibratorIds;
+            }
+            try {
+                if (mService == null) {
+                    Log.w(TAG, "Failed to retrieve vibrator ids; no vibrator manager service.");
+                } else {
+                    return mVibratorIds = mService.getVibratorIds();
+                }
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+            return new int[0];
+        }
+    }
+
+    @NonNull
+    @Override
+    public Vibrator getVibrator(int vibratorId) {
+        synchronized (mLock) {
+            Vibrator vibrator = mVibrators.get(vibratorId);
+            if (vibrator != null) {
+                return vibrator;
+            }
+            VibratorInfo info = null;
+            try {
+                if (mService == null) {
+                    Log.w(TAG, "Failed to retrieve vibrator; no vibrator manager service.");
+                } else {
+                    info = mService.getVibratorInfo(vibratorId);
+                }
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+            if (info != null) {
+                vibrator = new SingleVibrator(info);
+                mVibrators.put(vibratorId, vibrator);
+            } else {
+                vibrator = NullVibrator.getInstance();
+            }
+            return vibrator;
+        }
+    }
+
+    @NonNull
+    @Override
+    public Vibrator getDefaultVibrator() {
+        return mContext.getSystemService(Vibrator.class);
+    }
+
+    @Override
+    public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+            @Nullable CombinedVibrationEffect effect, @Nullable VibrationAttributes attributes) {
+        if (mService == null) {
+            Log.w(TAG, "Failed to set always-on effect; no vibrator manager service.");
+            return false;
+        }
+        try {
+            return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, effect, attributes);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to set always-on effect.", e);
+        }
+        return false;
+    }
+
+    @Override
+    public void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect,
+            String reason, @Nullable VibrationAttributes attributes) {
+        if (mService == null) {
+            Log.w(TAG, "Failed to vibrate; no vibrator manager service.");
+            return;
+        }
+        try {
+            mService.vibrate(uid, opPkg, effect, attributes, reason, mToken);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to vibrate.", e);
+        }
+    }
+
+    @Override
+    public void cancel() {
+        if (mService == null) {
+            Log.w(TAG, "Failed to cancel vibration; no vibrator manager service.");
+            return;
+        }
+        try {
+            mService.cancelVibrate(mToken);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to cancel vibration.", e);
+        }
+    }
+
+    /** Listener for vibrations on a single vibrator. */
+    private static class OnVibratorStateChangedListenerDelegate extends
+            IVibratorStateListener.Stub {
+        private final Executor mExecutor;
+        private final Vibrator.OnVibratorStateChangedListener mListener;
+
+        OnVibratorStateChangedListenerDelegate(
+                @NonNull Vibrator.OnVibratorStateChangedListener listener,
+                @NonNull Executor executor) {
+            mExecutor = executor;
+            mListener = listener;
+        }
+
+        @Override
+        public void onVibrating(boolean isVibrating) {
+            mExecutor.execute(() -> mListener.onVibratorStateChanged(isVibrating));
+        }
+    }
+
+    /** Controls vibrations on a single vibrator. */
+    private final class SingleVibrator extends Vibrator {
+        private final VibratorInfo mVibratorInfo;
+
+        SingleVibrator(@NonNull VibratorInfo vibratorInfo) {
+            mVibratorInfo = vibratorInfo;
+        }
+
+        @Override
+        public int getId() {
+            return mVibratorInfo.getId();
+        }
+
+        @Override
+        public boolean hasVibrator() {
+            return true;
+        }
+
+        @Override
+        public boolean hasAmplitudeControl() {
+            return mVibratorInfo.hasAmplitudeControl();
+        }
+
+        @NonNull
+        @Override
+        public int[] areEffectsSupported(@NonNull int... effectIds) {
+            int[] supported = new int[effectIds.length];
+            for (int i = 0; i < effectIds.length; i++) {
+                supported[i] = mVibratorInfo.isEffectSupported(effectIds[i]);
+            }
+            return supported;
+        }
+
+        @Override
+        public boolean[] arePrimitivesSupported(
+                @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
+            boolean[] supported = new boolean[primitiveIds.length];
+            for (int i = 0; i < primitiveIds.length; i++) {
+                supported[i] = mVibratorInfo.isPrimitiveSupported(primitiveIds[i]);
+            }
+            return supported;
+        }
+
+        @Override
+        public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+                @Nullable VibrationEffect effect, @Nullable AudioAttributes attributes) {
+            if (mService == null) {
+                Log.w(TAG, "Failed to set always-on effect on vibrator " + mVibratorInfo.getId()
+                        + "; no vibrator manager service.");
+                return false;
+            }
+            try {
+                VibrationAttributes attr = new VibrationAttributes.Builder(
+                        attributes, effect).build();
+                CombinedVibrationEffect combined = CombinedVibrationEffect.startSynced()
+                        .addVibrator(mVibratorInfo.getId(), effect)
+                        .combine();
+                return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combined, attr);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to set always-on effect on vibrator " + mVibratorInfo.getId());
+            }
+            return false;
+        }
+
+        @Override
+        public void vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe, String reason,
+                @NonNull VibrationAttributes attributes) {
+            if (mService == null) {
+                Log.w(TAG, "Failed to vibrate on vibrator " + mVibratorInfo.getId()
+                        + "; no vibrator manager service.");
+                return;
+            }
+            try {
+                CombinedVibrationEffect combined = CombinedVibrationEffect.startSynced()
+                        .addVibrator(mVibratorInfo.getId(), vibe)
+                        .combine();
+                mService.vibrate(uid, opPkg, combined, attributes, reason, mToken);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to vibrate.", e);
+            }
+        }
+
+        @Override
+        public void cancel() {
+            if (mService == null) {
+                Log.w(TAG, "Failed to cancel vibration on vibrator " + mVibratorInfo.getId()
+                        + "; no vibrator manager service.");
+                return;
+            }
+            try {
+                mService.cancelVibrate(mToken);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to cancel vibration on vibrator " + mVibratorInfo.getId(), e);
+            }
+        }
+
+        @Override
+        public boolean isVibrating() {
+            if (mService == null) {
+                Log.w(TAG, "Failed to check status of vibrator " + mVibratorInfo.getId()
+                        + "; no vibrator service.");
+                return false;
+            }
+            try {
+                return mService.isVibrating(mVibratorInfo.getId());
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+            return false;
+        }
+
+        @Override
+        public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+            Objects.requireNonNull(listener);
+            if (mContext == null) {
+                Log.w(TAG, "Failed to add vibrate state listener; no vibrator context.");
+                return;
+            }
+            addVibratorStateListener(mContext.getMainExecutor(), listener);
+        }
+
+        @Override
+        public void addVibratorStateListener(
+                @NonNull @CallbackExecutor Executor executor,
+                @NonNull OnVibratorStateChangedListener listener) {
+            Objects.requireNonNull(listener);
+            Objects.requireNonNull(executor);
+            if (mService == null) {
+                Log.w(TAG,
+                        "Failed to add vibrate state listener to vibrator " + mVibratorInfo.getId()
+                                + "; no vibrator service.");
+                return;
+            }
+            synchronized (mLock) {
+                // If listener is already registered, reject and return.
+                if (mListeners.containsKey(listener)) {
+                    Log.w(TAG, "Listener already registered.");
+                    return;
+                }
+                try {
+                    OnVibratorStateChangedListenerDelegate delegate =
+                            new OnVibratorStateChangedListenerDelegate(listener, executor);
+                    if (!mService.registerVibratorStateListener(mVibratorInfo.getId(), delegate)) {
+                        Log.w(TAG, "Failed to add vibrate state listener to vibrator "
+                                + mVibratorInfo.getId());
+                        return;
+                    }
+                    mListeners.put(listener, delegate);
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
+            }
+        }
+
+        @Override
+        public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+            Objects.requireNonNull(listener);
+            if (mService == null) {
+                Log.w(TAG, "Failed to remove vibrate state listener from vibrator "
+                        + mVibratorInfo.getId() + "; no vibrator service.");
+                return;
+            }
+            synchronized (mLock) {
+                // Check if the listener is registered, otherwise will return.
+                if (mListeners.containsKey(listener)) {
+                    OnVibratorStateChangedListenerDelegate delegate = mListeners.get(listener);
+                    try {
+                        if (!mService.unregisterVibratorStateListener(mVibratorInfo.getId(),
+                                delegate)) {
+                            Log.w(TAG, "Failed to remove vibrate state listener from vibrator "
+                                    + mVibratorInfo.getId());
+                            return;
+                        }
+                        mListeners.remove(listener);
+                    } catch (RemoteException e) {
+                        e.rethrowFromSystemServer();
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 7d85d13..d6fa733 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -184,6 +184,15 @@
     }
 
     /**
+     * Return the ID of this vibrator.
+     *
+     * @return The id of the vibrator controlled by this service.
+     */
+    public int getId() {
+        return -1;
+    }
+
+    /**
      * Check whether the hardware has a vibrator.
      *
      * @return True if the hardware has a vibrator, else false.
diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java
index 1d5a587..5dd38b6 100644
--- a/core/java/android/os/VibratorManager.java
+++ b/core/java/android/os/VibratorManager.java
@@ -17,45 +17,123 @@
 package android.os;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.app.ActivityThread;
+import android.content.Context;
+import android.util.Log;
 
 /**
- * VibratorManager provides access to multiple vibrators, as well as the ability to run them in
- * a synchronized fashion.
+ * Class that provides access to all vibrators from the device, as well as the ability to run them
+ * in a synchronized fashion.
+ * <p>
+ * If your process exits, any vibration you started will stop.
+ * </p>
  */
+@SystemService(Context.VIBRATOR_MANAGER_SERVICE)
 public abstract class VibratorManager {
-    /** @hide */
-    protected static final String TAG = "VibratorManager";
+    private static final String TAG = "VibratorManager";
+
+    private final String mPackageName;
 
     /**
-     * {@hide}
+     * @hide to prevent subclassing from outside of the framework
      */
     public VibratorManager() {
+        mPackageName = ActivityThread.currentPackageName();
     }
 
     /**
-     * This method lists all available actuator ids, returning a possible empty list.
-     * If the device has only a single actuator, this should return a single entry with a
-     * default id.
+     * @hide to prevent subclassing from outside of the framework
+     */
+    protected VibratorManager(Context context) {
+        mPackageName = context.getOpPackageName();
+    }
+
+    /**
+     * List all available vibrator ids, returning a possible empty list.
+     *
+     * @return An array containing the ids of the vibrators available on the device.
      */
     @NonNull
     public abstract int[] getVibratorIds();
 
     /**
-    * Returns a Vibrator service for given id.
-    * This allows users to perform a vibration effect on a single actuator.
-    */
+     * Retrieve a single vibrator by id.
+     *
+     * @param vibratorId The id of the vibrator to be retrieved.
+     * @return The vibrator with given {@code vibratorId}, never null.
+     */
     @NonNull
     public abstract Vibrator getVibrator(int vibratorId);
 
     /**
-    * Returns the system default Vibrator service.
-    */
+     * Returns the system default Vibrator service.
+     */
     @NonNull
     public abstract Vibrator getDefaultVibrator();
 
     /**
-     * Vibrates all actuators by passing each VibrationEffect within CombinedVibrationEffect
-     * to the respective actuator, in sync.
+     * Configure an always-on haptics effect.
+     *
+     * @hide
      */
-    public abstract void vibrate(@NonNull CombinedVibrationEffect effect);
+    @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
+    public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+            @Nullable CombinedVibrationEffect effect, @Nullable VibrationAttributes attributes) {
+        Log.w(TAG, "Always-on effects aren't supported");
+        return false;
+    }
+
+    /**
+     * Vibrate with a given combination of effects.
+     *
+     * <p>
+     * Pass in a {@link CombinedVibrationEffect} representing a combination of {@link
+     * VibrationEffect} to be played on one or more vibrators.
+     * </p>
+     *
+     * @param effect an array of longs of times for which to turn the vibrator on or off.
+     */
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    public final void vibrate(@NonNull CombinedVibrationEffect effect) {
+        vibrate(effect, null);
+    }
+
+    /**
+     * Vibrate with a given combination of effects.
+     *
+     * <p>
+     * Pass in a {@link CombinedVibrationEffect} representing a combination of {@link
+     * VibrationEffect} to be played on one or more vibrators.
+     * </p>
+     *
+     * @param effect     an array of longs of times for which to turn the vibrator on or off.
+     * @param attributes {@link VibrationAttributes} corresponding to the vibration. For example,
+     *                   specify {@link VibrationAttributes#USAGE_ALARM} for alarm vibrations or
+     *                   {@link VibrationAttributes#USAGE_RINGTONE} for vibrations associated with
+     *                   incoming calls.
+     */
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    public final void vibrate(@NonNull CombinedVibrationEffect effect,
+            @Nullable VibrationAttributes attributes) {
+        vibrate(Process.myUid(), mPackageName, effect, null, attributes);
+    }
+
+    /**
+     * Like {@link #vibrate(CombinedVibrationEffect, VibrationAttributes)}, but allows the
+     * caller to specify the vibration is owned by someone else and set reason for vibration.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    public abstract void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect,
+            String reason, @Nullable VibrationAttributes attributes);
+
+    /**
+     * Turn all the vibrators off.
+     */
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    public abstract void cancel();
 }
diff --git a/core/java/android/view/SurfaceSession.java b/core/java/android/view/SurfaceSession.java
index cbc0479..20f0598 100644
--- a/core/java/android/view/SurfaceSession.java
+++ b/core/java/android/view/SurfaceSession.java
@@ -32,7 +32,6 @@
 
     private static native long nativeCreate();
     private static native void nativeDestroy(long ptr);
-    private static native void nativeKill(long ptr);
 
     /** Create a new connection with the surface flinger. */
     @UnsupportedAppUsage
@@ -44,22 +43,22 @@
     @Override
     protected void finalize() throws Throwable {
         try {
-            if (mNativeClient != 0) {
-                nativeDestroy(mNativeClient);
-            }
+            kill();
         } finally {
             super.finalize();
         }
     }
 
     /**
-     * Forcibly detach native resources associated with this object.
-     * Unlike destroy(), after this call any surfaces that were created
-     * from the session will no longer work.
+     * Remove the reference to the native Session object. The native object may still exist if
+     * there are other references to it, but it cannot be accessed from this Java object anymore.
      */
     @UnsupportedAppUsage
     public void kill() {
-        nativeKill(mNativeClient);
+        if (mNativeClient != 0) {
+            nativeDestroy(mNativeClient);
+            mNativeClient = 0;
+        }
     }
 }
 
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 70ec2d4..6eba83f 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1083,7 +1083,12 @@
 
                 if (creating) {
                     updateOpaqueFlag();
-                    mDeferredDestroySurfaceControl = createSurfaceControls(viewRoot);
+                    final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
+                    if (mUseBlastAdapter) {
+                        createBlastSurfaceControls(viewRoot, name);
+                    } else {
+                        mDeferredDestroySurfaceControl = createSurfaceControls(viewRoot, name);
+                    }
                 } else if (mSurfaceControl == null) {
                     return;
                 }
@@ -1220,53 +1225,77 @@
      * out, the old surface can be persevered until the new one has drawn by keeping the reference
      * of the old SurfaceControl alive.
      */
-    private SurfaceControl createSurfaceControls(ViewRootImpl viewRoot) {
-        final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
-
-        SurfaceControl.Builder builder = new SurfaceControl.Builder(mSurfaceSession)
+    private SurfaceControl createSurfaceControls(ViewRootImpl viewRoot, String name) {
+        final SurfaceControl previousSurfaceControl = mSurfaceControl;
+        mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
                 .setName(name)
                 .setLocalOwnerView(this)
                 .setParent(viewRoot.getBoundsLayer())
-                .setCallsite("SurfaceView.updateSurface");
+                .setCallsite("SurfaceView.updateSurface")
+                .setBufferSize(mSurfaceWidth, mSurfaceHeight)
+                .setFlags(mSurfaceFlags)
+                .setFormat(mFormat)
+                .build();
+        mBackgroundControl = createBackgroundControl(name);
+        return previousSurfaceControl;
+    }
 
-        final SurfaceControl previousSurfaceControl;
-        if (mUseBlastAdapter) {
-            mSurfaceControl = builder
+    private SurfaceControl createBackgroundControl(String name) {
+        return new SurfaceControl.Builder(mSurfaceSession)
+        .setName("Background for " + name)
+        .setLocalOwnerView(this)
+        .setOpaque(true)
+        .setColorLayer()
+        .setParent(mSurfaceControl)
+        .setCallsite("SurfaceView.updateSurface")
+        .build();
+    }
+
+    // We don't recreate the surface controls but only recreate the adapter. Since the blast layer
+    // is still alive, the old buffers will continue to be presented until replaced by buffers from
+    // the new adapter. This means we do not need to track the old surface control and destroy it
+    // after the client has drawn to avoid any flickers.
+    private void createBlastSurfaceControls(ViewRootImpl viewRoot, String name) {
+        if (mSurfaceControl == null) {
+            mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
+                    .setName(name)
+                    .setLocalOwnerView(this)
+                    .setParent(viewRoot.getBoundsLayer())
+                    .setCallsite("SurfaceView.updateSurface")
                     .setContainerLayer()
                     .build();
-            previousSurfaceControl = mBlastSurfaceControl;
+        }
+
+        if (mBlastSurfaceControl == null) {
             mBlastSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
                     .setName(name + "(BLAST)")
                     .setLocalOwnerView(this)
-                    .setBufferSize(mSurfaceWidth, mSurfaceHeight)
                     .setParent(mSurfaceControl)
                     .setFlags(mSurfaceFlags)
                     .setHidden(false)
                     .setBLASTLayer()
                     .setCallsite("SurfaceView.updateSurface")
                     .build();
-            mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth,
-                    mSurfaceHeight, mFormat, true /* TODO */);
         } else {
-            previousSurfaceControl = mSurfaceControl;
-            mSurfaceControl = builder
-                    .setBufferSize(mSurfaceWidth, mSurfaceHeight)
-                    .setFlags(mSurfaceFlags)
-                    .setFormat(mFormat)
-                    .build();
-            mBlastSurfaceControl = null;
-            mBlastBufferQueue = null;
+            // update blast layer
+            mTmpTransaction
+                    .setOpaque(mBlastSurfaceControl, (mSurfaceFlags & SurfaceControl.OPAQUE) != 0)
+                    .setSecure(mBlastSurfaceControl, (mSurfaceFlags & SurfaceControl.SECURE) != 0)
+                    .show(mBlastSurfaceControl)
+                    .apply();
         }
-        mBackgroundControl = new SurfaceControl.Builder(mSurfaceSession)
-            .setName("Background for " + name)
-            .setLocalOwnerView(this)
-            .setOpaque(true)
-            .setColorLayer()
-            .setParent(mSurfaceControl)
-            .setCallsite("SurfaceView.updateSurface")
-            .build();
 
-        return previousSurfaceControl;
+        if (mBackgroundControl == null) {
+            mBackgroundControl = createBackgroundControl(name);
+        }
+
+        // Always recreate the IGBP for compatibility. This can be optimized in the future but
+        // the behavior change will need to be gated by SDK version.
+        if (mBlastBufferQueue != null) {
+            mBlastBufferQueue.destroy();
+        }
+        mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth,
+                mSurfaceHeight, mFormat, true /* TODO */);
     }
 
     private void onDrawFinished() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 4716141..6b13a29 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1898,11 +1898,12 @@
     }
 
     private void setBoundsLayerCrop(Transaction t) {
-        // mWinFrame is already adjusted for surface insets. So offset it and use it as
-        // the cropping bounds.
-        mTempBoundsRect.set(mWinFrame);
-        mTempBoundsRect.offsetTo(mWindowAttributes.surfaceInsets.left,
-                mWindowAttributes.surfaceInsets.top);
+        // Adjust of insets and update the bounds layer so child surfaces do not draw into
+        // the surface inset region.
+        mTempBoundsRect.set(0, 0, mSurfaceSize.x, mSurfaceSize.y);
+        mTempBoundsRect.inset(mWindowAttributes.surfaceInsets.left,
+                mWindowAttributes.surfaceInsets.top,
+                mWindowAttributes.surfaceInsets.right, mWindowAttributes.surfaceInsets.bottom);
         t.setWindowCrop(mBoundsLayer, mTempBoundsRect);
     }
 
@@ -1913,25 +1914,18 @@
     private boolean updateBoundsLayer(SurfaceControl.Transaction t) {
         if (mBoundsLayer != null) {
             setBoundsLayerCrop(t);
-            t.deferTransactionUntil(mBoundsLayer, getSurfaceControl(),
-                mSurface.getNextFrameNumber());
             return true;
         }
         return false;
     }
 
-    private void prepareSurfaces(boolean sizeChanged) {
+    private void prepareSurfaces() {
         final SurfaceControl.Transaction t = mTransaction;
         final SurfaceControl sc = getSurfaceControl();
         if (!sc.isValid()) return;
 
-        boolean applyTransaction = updateBoundsLayer(t);
-        if (sizeChanged) {
-            applyTransaction = true;
-            t.setBufferSize(sc, mSurfaceSize.x, mSurfaceSize.y);
-        }
-        if (applyTransaction) {
-            t.apply();
+        if (updateBoundsLayer(t)) {
+              mergeWithNextTransaction(t, mSurface.getNextFrameNumber());
         }
     }
 
@@ -3036,7 +3030,7 @@
             // stopping, but on the client side it doesn't get stopped since it's restarted quick
             // enough. WMS doesn't want to keep around old children since they will leak when the
             // client creates new children.
-            prepareSurfaces(surfaceSizeChanged);
+            prepareSurfaces();
         }
 
         final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
@@ -3070,8 +3064,10 @@
                     // via the WM relayout code path. We probably eventually
                     // want to synchronize transparent region hint changes
                     // with draws.
-                    mTransaction.setTransparentRegionHint(getSurfaceControl(),
-                        mTransparentRegion).apply();
+                    SurfaceControl sc = getSurfaceControl();
+                    if (sc.isValid()) {
+                        mTransaction.setTransparentRegionHint(sc, mTransparentRegion).apply();
+                    }
                 }
             }
 
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 90c8e17..7b2bb73 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -115,7 +115,6 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -1083,19 +1082,6 @@
         }
     }
 
-    private static class ImeThreadFactory implements ThreadFactory {
-        private final String mThreadName;
-
-        ImeThreadFactory(String name) {
-            mThreadName = name;
-        }
-
-        @Override
-        public Thread newThread(Runnable r) {
-            return new Thread(r, mThreadName);
-        }
-    }
-
     final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
         @Override
         protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 61ff36c..681fd14 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -18,6 +18,7 @@
 
 import android.annotation.ColorInt;
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -27,6 +28,7 @@
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.os.Build;
+import android.util.AttributeSet;
 import android.view.animation.AnimationUtils;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
@@ -111,11 +113,14 @@
     private float mGlowAlpha;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private float mGlowScaleY;
+    private float mDistance;
 
     private float mGlowAlphaStart;
     private float mGlowAlphaFinish;
     private float mGlowScaleYStart;
     private float mGlowScaleYFinish;
+    private float mDistanceStart;
+    private float mDistanceFinish;
 
     private long mStartTime;
     private float mDuration;
@@ -150,9 +155,18 @@
      * @param context Context used to provide theming and resource information for the EdgeEffect
      */
     public EdgeEffect(Context context) {
+        this(context, null);
+    }
+
+    /**
+     * Construct a new EdgeEffect with a theme appropriate for the provided context.
+     * @param context Context used to provide theming and resource information for the EdgeEffect
+     * @param attrs The attributes of the XML tag that is inflating the view
+     */
+    public EdgeEffect(@NonNull Context context, @Nullable AttributeSet attrs) {
         mPaint.setAntiAlias(true);
         final TypedArray a = context.obtainStyledAttributes(
-                com.android.internal.R.styleable.EdgeEffect);
+                attrs, com.android.internal.R.styleable.EdgeEffect);
         final int themeColor = a.getColor(
                 com.android.internal.R.styleable.EdgeEffect_colorEdgeEffect, 0xff666666);
         mEdgeEffectType = a.getInt(
@@ -248,6 +262,7 @@
         mDuration = PULL_TIME;
 
         mPullDistance += deltaDistance;
+        mDistanceStart = mDistanceFinish = mDistance = Math.max(0f, mPullDistance);
 
         final float absdd = Math.abs(deltaDistance);
         mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA,
@@ -267,6 +282,56 @@
     }
 
     /**
+     * A view should call this when content is pulled away from an edge by the user.
+     * This will update the state of the current visual effect and its associated animation.
+     * The host view should always {@link android.view.View#invalidate()} after this
+     * and draw the results accordingly. This works similarly to {@link #onPull(float, float)},
+     * but returns the amount of <code>deltaDistance</code> that has been consumed. If the
+     * {@link #getDistance()} is currently 0 and <code>deltaDistance</code> is negative, this
+     * function will return 0 and the drawn value will remain unchanged.
+     *
+     * This method can be used to reverse the effect from a pull or absorb and partially consume
+     * some of a motion:
+     *
+     * <pre class="prettyprint">
+     *     if (deltaY < 0) {
+     *         float consumed = edgeEffect.onPullDistance(deltaY / getHeight(), x / getWidth());
+     *         deltaY -= consumed * getHeight();
+     *         if (edgeEffect.getDistance() == 0f) edgeEffect.onRelease();
+     *     }
+     * </pre>
+     *
+     * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to
+     *                      1.f (full length of the view) or negative values to express change
+     *                      back toward the edge reached to initiate the effect.
+     * @param displacement The displacement from the starting side of the effect of the point
+     *                     initiating the pull. In the case of touch this is the finger position.
+     *                     Values may be from 0-1.
+     * @return The amount of <code>deltaDistance</code> that was consumed, a number between
+     * 0 and <code>deltaDistance</code>.
+     */
+    public float onPullDistance(float deltaDistance, float displacement) {
+        float finalDistance = Math.max(0f, deltaDistance + mDistance);
+        float delta = finalDistance - mDistance;
+        onPull(delta, displacement);
+        return delta;
+    }
+
+    /**
+     * Returns the pull distance needed to be released to remove the showing effect.
+     * It is determined by the {@link #onPull(float, float)} <code>deltaDistance</code> and
+     * any animating values, including from {@link #onAbsorb(int)} and {@link #onRelease()}.
+     *
+     * This can be used in conjunction with {@link #onPullDistance(float, float)} to
+     * release the currently showing effect.
+     *
+     * @return The pull distance that must be released to remove the showing effect.
+     */
+    public float getDistance() {
+        return mDistance;
+    }
+
+    /**
      * Call when the object is released after being pulled.
      * This will begin the "decay" phase of the effect. After calling this method
      * the host view should {@link android.view.View#invalidate()} and thereby
@@ -282,9 +347,11 @@
         mState = STATE_RECEDE;
         mGlowAlphaStart = mGlowAlpha;
         mGlowScaleYStart = mGlowScaleY;
+        mDistanceStart = mDistance;
 
         mGlowAlphaFinish = 0.f;
         mGlowScaleYFinish = 0.f;
+        mDistanceFinish = 0.f;
 
         mStartTime = AnimationUtils.currentAnimationTimeMillis();
         mDuration = RECEDE_TIME;
@@ -311,7 +378,7 @@
         // nearly invisible.
         mGlowAlphaStart = GLOW_ALPHA_START;
         mGlowScaleYStart = Math.max(mGlowScaleY, 0.f);
-
+        mDistanceStart = mDistance;
 
         // Growth for the size of the glow should be quadratic to properly
         // respond
@@ -322,6 +389,9 @@
         mGlowAlphaFinish = Math.max(
                 mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA));
         mTargetDisplacement = 0.5f;
+
+        // Use glow values to estimate the absorption for stretch distance.
+        mDistanceFinish = calculateDistanceFromGlowValues(mGlowScaleYFinish, mGlowAlphaFinish);
     }
 
     /**
@@ -447,6 +517,7 @@
 
         mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp;
         mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp;
+        mDistance = mDistanceStart + (mDistanceFinish - mDistanceStart) * interp;
         mDisplacement = (mDisplacement + mTargetDisplacement) / 2;
 
         if (t >= 1.f - EPSILON) {
@@ -458,10 +529,12 @@
 
                     mGlowAlphaStart = mGlowAlpha;
                     mGlowScaleYStart = mGlowScaleY;
+                    mDistanceStart = mDistance;
 
                     // After absorb, the glow should fade to nothing.
                     mGlowAlphaFinish = 0.f;
                     mGlowScaleYFinish = 0.f;
+                    mDistanceFinish = 0.f;
                     break;
                 case STATE_PULL:
                     mState = STATE_PULL_DECAY;
@@ -470,10 +543,12 @@
 
                     mGlowAlphaStart = mGlowAlpha;
                     mGlowScaleYStart = mGlowScaleY;
+                    mDistanceStart = mDistance;
 
                     // After pull, the glow should fade to nothing.
                     mGlowAlphaFinish = 0.f;
                     mGlowScaleYFinish = 0.f;
+                    mDistanceFinish = 0.f;
                     break;
                 case STATE_PULL_DECAY:
                     mState = STATE_RECEDE;
@@ -484,4 +559,20 @@
             }
         }
     }
+
+    /**
+     * @return The estimated pull distance as calculated from mGlowScaleY.
+     */
+    private float calculateDistanceFromGlowValues(float scale, float alpha) {
+        if (scale >= 1f) {
+            // It should asymptotically approach 1, but not reach there.
+            // Here, we're just choosing a value that is large.
+            return 1f;
+        }
+        if (scale > 0f) {
+            float v = 1f / 0.7f / (mGlowScaleY - 1f);
+            return v * v / mBounds.height();
+        }
+        return alpha / PULL_DISTANCE_ALPHA_GLOW_FACTOR;
+    }
 }
diff --git a/core/java/android/widget/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java
index b484dfa..2904a8c 100644
--- a/core/java/android/widget/ToastPresenter.java
+++ b/core/java/android/widget/ToastPresenter.java
@@ -172,6 +172,22 @@
     }
 
     /**
+     * Update the LayoutParameters of the currently showing toast view. This is used for layout
+     * updates based on orientation changes.
+     */
+    public void updateLayoutParams(int xOffset, int yOffset, float horizontalMargin,
+            float verticalMargin, int gravity) {
+        checkState(mView != null, "Toast must be showing to update its layout parameters.");
+        Configuration config = mResources.getConfiguration();
+        mParams.gravity = Gravity.getAbsoluteGravity(gravity, config.getLayoutDirection());
+        mParams.x = xOffset;
+        mParams.y = yOffset;
+        mParams.horizontalMargin = horizontalMargin;
+        mParams.verticalMargin = verticalMargin;
+        addToastView();
+    }
+
+    /**
      * Sets {@link WindowManager.LayoutParams#SYSTEM_FLAG_SHOW_FOR_ALL_USERS} flag if {@code
      * packageName} is a cross-user package.
      *
@@ -221,18 +237,7 @@
 
         adjustLayoutParams(mParams, windowToken, duration, gravity, xOffset, yOffset,
                 horizontalMargin, verticalMargin, removeWindowAnimations);
-        if (mView.getParent() != null) {
-            mWindowManager.removeView(mView);
-        }
-        try {
-            mWindowManager.addView(mView, mParams);
-        } catch (WindowManager.BadTokenException e) {
-            // Since the notification manager service cancels the token right after it notifies us
-            // to cancel the toast there is an inherent race and we may attempt to add a window
-            // after the token has been invalidated. Let us hedge against that.
-            Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e);
-            return;
-        }
+        addToastView();
         trySendAccessibilityEvent(mView, mPackageName);
         if (callback != null) {
             try {
@@ -288,4 +293,19 @@
         view.dispatchPopulateAccessibilityEvent(event);
         mAccessibilityManager.sendAccessibilityEvent(event);
     }
+
+    private void addToastView() {
+        if (mView.getParent() != null) {
+            mWindowManager.removeView(mView);
+        }
+        try {
+            mWindowManager.addView(mView, mParams);
+        } catch (WindowManager.BadTokenException e) {
+            // Since the notification manager service cancels the token right after it notifies us
+            // to cancel the toast there is an inherent race and we may attempt to add a window
+            // after the token has been invalidated. Let us hedge against that.
+            Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e);
+            return;
+        }
+    }
 }
diff --git a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
index c2ee646..0152387 100644
--- a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
+++ b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
@@ -53,10 +53,8 @@
     public static final String KEY_CUR_TASK = "cur_task";
     /** Package of newly requested heavy-weight app. */
     public static final String KEY_NEW_APP = "new_app";
-    public static final String KEY_ACTIVITY_OPTIONS = "activity_options";
     
     IntentSender mStartIntent;
-    Bundle mActivityOptions;
     boolean mHasResult;
     String mCurApp;
     int mCurTask;
@@ -67,9 +65,8 @@
         super.onCreate(savedInstanceState);
         
         requestWindowFeature(Window.FEATURE_NO_TITLE);
-
+        
         mStartIntent = (IntentSender)getIntent().getParcelableExtra(KEY_INTENT);
-        mActivityOptions = getIntent().getBundleExtra(KEY_ACTIVITY_OPTIONS);
         mHasResult = getIntent().getBooleanExtra(KEY_HAS_RESULT, false);
         mCurApp = getIntent().getStringExtra(KEY_CUR_APP);
         mCurTask = getIntent().getIntExtra(KEY_CUR_TASK, 0);
@@ -151,9 +148,9 @@
                 if (mHasResult) {
                     startIntentSenderForResult(mStartIntent, -1, null,
                             Intent.FLAG_ACTIVITY_FORWARD_RESULT,
-                            Intent.FLAG_ACTIVITY_FORWARD_RESULT, 0, mActivityOptions);
+                            Intent.FLAG_ACTIVITY_FORWARD_RESULT, 0);
                 } else {
-                    startIntentSenderForResult(mStartIntent, -1, null, 0, 0, 0, mActivityOptions);
+                    startIntentSenderForResult(mStartIntent, -1, null, 0, 0, 0);
                 }
             } catch (IntentSender.SendIntentException ex) {
                 Log.w("HeavyWeightSwitcherActivity", "Failure starting", ex);
diff --git a/core/java/com/android/internal/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
similarity index 99%
rename from core/java/com/android/internal/BrightnessSynchronizer.java
rename to core/java/com/android/internal/display/BrightnessSynchronizer.java
index 9049ca5..fae5862 100644
--- a/core/java/com/android/internal/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.internal;
+package com.android.internal.display;
 
 
 import android.content.ContentResolver;
diff --git a/core/java/com/android/internal/display/OWNERS b/core/java/com/android/internal/display/OWNERS
new file mode 100644
index 0000000..20b75be
--- /dev/null
+++ b/core/java/com/android/internal/display/OWNERS
@@ -0,0 +1,3 @@
+include /services/core/java/com/android/server/display/OWNERS
+
+flc@google.com
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 6e41b3f..96a71b9 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -7157,20 +7157,34 @@
 
     @Override
     public long getScreenOnEnergy() {
-        if (mGlobalMeasuredEnergyStats == null) {
-            return ENERGY_DATA_UNAVAILABLE;
-        }
-        return mGlobalMeasuredEnergyStats
-                .getAccumulatedStandardBucketEnergy(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON);
+        return getMeasuredEnergyMicroJoules(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON);
     }
 
     @Override
     public long getScreenDozeEnergy() {
+        return getMeasuredEnergyMicroJoules(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE);
+    }
+
+    /**
+     * Returns the energy in microjoules that the given standard energy bucket consumed.
+     * Will return {@link #ENERGY_DATA_UNAVAILABLE} if data is unavailable
+     *
+     * @param bucket standard energy bucket of interest
+     * @return energy (in microjoules) used for this energy bucket
+     */
+    private long getMeasuredEnergyMicroJoules(@StandardEnergyBucket int bucket) {
         if (mGlobalMeasuredEnergyStats == null) {
             return ENERGY_DATA_UNAVAILABLE;
         }
-        return mGlobalMeasuredEnergyStats
-                .getAccumulatedStandardBucketEnergy(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE);
+        return mGlobalMeasuredEnergyStats.getAccumulatedStandardBucketEnergy(bucket);
+    }
+
+    @Override
+    public @Nullable long[] getCustomMeasuredEnergiesMicroJoules() {
+        if (mGlobalMeasuredEnergyStats == null) {
+            return null;
+        }
+        return mGlobalMeasuredEnergyStats.getAccumulatedCustomBucketEnergies();
     }
 
     @Override public long getStartClockTime() {
@@ -7941,6 +7955,13 @@
                     .updateStandardBucket(energyBucket, energyDeltaUJ, accumulate);
         }
 
+        /** Adds the given energy to the given custom energy bucket for this uid. */
+        private void addEnergyToCustomBucketLocked(long energyDeltaUJ, int energyBucket,
+                boolean accumulate) {
+            getOrCreateMeasuredEnergyStatsLocked()
+                    .updateCustomBucket(energyBucket, energyDeltaUJ, accumulate);
+        }
+
         /**
          * Returns the energy used by this uid for a standard energy bucket of interest.
          * @param bucket standard energy bucket of interest
@@ -7957,6 +7978,18 @@
             return mUidMeasuredEnergyStats.getAccumulatedStandardBucketEnergy(bucket);
         }
 
+        @Override
+        public long[] getCustomMeasuredEnergiesMicroJoules() {
+            if (mBsi.mGlobalMeasuredEnergyStats == null) {
+                return null;
+            }
+            if (mUidMeasuredEnergyStats == null) {
+                // Custom buckets may exist. But all values for this uid are 0 so we report all 0s.
+                return new long[mBsi.mGlobalMeasuredEnergyStats.getNumberCustomEnergyBuckets()];
+            }
+            return mUidMeasuredEnergyStats.getAccumulatedCustomBucketEnergies();
+        }
+
         /**
          * Gets the minimum of the uid's foreground activity time and its PROCESS_STATE_TOP time
          * since last marked. Also sets the mark time for both these timers.
@@ -12465,6 +12498,42 @@
     }
 
     /**
+     * Accumulate Custom energy bucket energy, globally and for each app.
+     *
+     * @param totalEnergyUJ energy (microjoules) used for this bucket since this was last called.
+     * @param uidEnergies map of uid->energy (microjoules) for this bucket since last called.
+     *                    Data inside uidEnergies will not be modified (treated immutable).
+     */
+    public void updateCustomMeasuredEnergyDataLocked(int customEnergyBucket,
+            long totalEnergyUJ, @Nullable SparseLongArray uidEnergies) {
+        if (DEBUG_ENERGY) {
+            Slog.d(TAG, "Updating attributed measured energy stats for custom bucket "
+                    + customEnergyBucket
+                    + " with total energy " + totalEnergyUJ
+                    + " and uid energies " + String.valueOf(uidEnergies));
+        }
+        if (mGlobalMeasuredEnergyStats == null) return;
+        if (!mOnBatteryInternal || mIgnoreNextExternalStats || totalEnergyUJ <= 0) return;
+
+        mGlobalMeasuredEnergyStats.updateCustomBucket(customEnergyBucket, totalEnergyUJ, true);
+
+        if (uidEnergies == null) return;
+        final int numUids = uidEnergies.size();
+        for (int i = 0; i < numUids; i++) {
+            final int uidInt = mapUid(uidEnergies.keyAt(i));
+            final long uidEnergyUJ = uidEnergies.valueAt(i);
+            if (uidEnergyUJ == 0) continue;
+            // TODO: Worry about uids not in BSI currently, including uninstalled uids 'coming back'
+            //  Specifically: What if the uid had been removed? We'll re-create it now.
+            //  And if we instead use getAvailableUidStatsLocked() and chec for null, then we might
+            //  not create a Uid even when we should be (say, the app's first event, somehow, was to
+            //  use GPU). I guess that CPU/kernel data might already have this problem?
+            final Uid uidObj = getUidStatsLocked(uidInt);
+            uidObj.addEnergyToCustomBucketLocked(uidEnergyUJ, customEnergyBucket, true);
+        }
+    }
+
+    /**
      * Read and record Rail Energy data.
      */
     public void updateRailStatsLocked() {
@@ -14454,7 +14523,12 @@
      */
     @GuardedBy("this")
     public void dumpMeasuredEnergyStatsLocked(PrintWriter pw) {
-        if (mGlobalMeasuredEnergyStats == null) return;
+        pw.printf("On battery measured energy stats (microjoules) \n");
+        if (mGlobalMeasuredEnergyStats == null) {
+            pw.printf("    Not supported on this device.\n");
+            return;
+        }
+
         dumpMeasuredEnergyStatsLocked(pw, "non-uid usage", mGlobalMeasuredEnergyStats);
 
         int size = mUidStats.size();
@@ -14472,7 +14546,8 @@
             MeasuredEnergyStats stats) {
         if (stats == null) return;
         final IndentingPrintWriter iPw = new IndentingPrintWriter(pw, "    ");
-        iPw.printf("On battery measured energy stats for %s:\n", name);
+        iPw.increaseIndent();
+        iPw.printf("%s:\n", name);
         iPw.increaseIndent();
         stats.dump(iPw);
         iPw.decreaseIndent();
diff --git a/core/java/com/android/internal/power/MeasuredEnergyArray.java b/core/java/com/android/internal/power/MeasuredEnergyArray.java
deleted file mode 100644
index 1f6dc26..0000000
--- a/core/java/com/android/internal/power/MeasuredEnergyArray.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.power;
-
-
-import android.annotation.IntDef;
-
-import com.android.internal.os.RailStats;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Interface to provide subsystem energy data.
- * TODO: replace this and {@link RailStats} once b/173077356 is done
- */
-public interface MeasuredEnergyArray {
-    int SUBSYSTEM_UNKNOWN = -1;
-    int SUBSYSTEM_DISPLAY = 0;
-    int NUMBER_SUBSYSTEMS = 1;
-    String[] SUBSYSTEM_NAMES = {"display"};
-
-
-    @IntDef(prefix = { "SUBSYSTEM_" }, value = {
-            SUBSYSTEM_UNKNOWN,
-            SUBSYSTEM_DISPLAY,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface MeasuredEnergySubsystem {}
-
-    /**
-     * Get the subsystem at an index in array.
-     *
-     * @param index into the array.
-     * @return subsystem.
-     */
-    @MeasuredEnergySubsystem
-    int getSubsystem(int index);
-
-    /**
-     * Get the energy (in microjoules) consumed since boot of the subsystem at an index.
-     *
-     * @param index into the array.
-     * @return energy (in microjoules) consumed since boot.
-     */
-    long getEnergy(int index);
-
-    /**
-     * Return number of subsystems in the array.
-     */
-    int size();
-}
diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java
index e310f8d..d7b4d78 100644
--- a/core/java/com/android/internal/power/MeasuredEnergyStats.java
+++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java
@@ -28,7 +28,6 @@
 import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.power.MeasuredEnergyArray.MeasuredEnergySubsystem;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -239,6 +238,7 @@
      * Return accumulated energy (in microjoules) for the a custom energy bucket since last reset.
      * Returns {@link android.os.BatteryStats#ENERGY_DATA_UNAVAILABLE} if this data is unavailable.
      */
+    @VisibleForTesting
     public long getAccumulatedCustomBucketEnergy(int customBucket) {
         if (!isValidCustomBucket(customBucket)) {
             return ENERGY_DATA_UNAVAILABLE;
@@ -247,7 +247,18 @@
     }
 
     /**
-     * Map {@link MeasuredEnergySubsystem} and device state to Display {@link StandardEnergyBucket}.
+     * Return accumulated energies (in microjoules) for all custom energy buckets since last reset.
+     */
+    public @NonNull long[] getAccumulatedCustomBucketEnergies() {
+        final long[] energies = new long[getNumberCustomEnergyBuckets()];
+        for (int bucket = 0; bucket < energies.length; bucket++) {
+            energies[bucket] = mAccumulatedEnergiesMicroJoules[customBucketToIndex(bucket)];
+        }
+        return energies;
+    }
+
+    /**
+     * Map {@link android.view.Display} STATE_ to corresponding {@link StandardEnergyBucket}.
      */
     public static @StandardEnergyBucket int getDisplayEnergyBucket(int screenState) {
         if (Display.isOnState(screenState)) {
@@ -405,7 +416,6 @@
 
     /** Dump debug data. */
     public void dump(PrintWriter pw) {
-        pw.println("Accumulated energy since last reset (microjoules):");
         pw.print("   ");
         for (int index = 0; index < mAccumulatedEnergiesMicroJoules.length; index++) {
             pw.print(getBucketName(index));
@@ -432,6 +442,11 @@
         return "CUSTOM_" + indexToCustomBucket(index);
     }
 
+    /** Get the number of custom energy buckets on this device. */
+    public int getNumberCustomEnergyBuckets() {
+        return mAccumulatedEnergiesMicroJoules.length - NUMBER_STANDARD_ENERGY_BUCKETS;
+    }
+
     private static int customBucketToIndex(int customBucket) {
         return customBucket + NUMBER_STANDARD_ENERGY_BUCKETS;
     }
@@ -450,7 +465,9 @@
         return bucket >= 0 && bucket < NUMBER_STANDARD_ENERGY_BUCKETS;
     }
 
-    private boolean isValidCustomBucket(int customBucket) {
+    /** Returns whether the given custom bucket is valid (exists) on this device. */
+    @VisibleForTesting
+    public boolean isValidCustomBucket(int customBucket) {
         return customBucket >= 0
                 && customBucketToIndex(customBucket) < mAccumulatedEnergiesMicroJoules.length;
     }
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index af06e2e..c7439f1 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -354,7 +354,7 @@
 }
 
 static jlong NativeGetFinalizer(JNIEnv* /*env*/, jclass /*clazz*/) {
-  return reinterpret_cast<jlong>(&NativeDestroy);
+  return static_cast<jlong>(reinterpret_cast<uintptr_t>(&NativeDestroy));
 }
 
 static jstring NativeGetAssetPath(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
diff --git a/core/jni/android_view_SurfaceSession.cpp b/core/jni/android_view_SurfaceSession.cpp
index 191f748..0aac07d 100644
--- a/core/jni/android_view_SurfaceSession.cpp
+++ b/core/jni/android_view_SurfaceSession.cpp
@@ -51,19 +51,12 @@
     client->decStrong((void*)nativeCreate);
 }
 
-static void nativeKill(JNIEnv* env, jclass clazz, jlong ptr) {
-    SurfaceComposerClient* client = reinterpret_cast<SurfaceComposerClient*>(ptr);
-    client->dispose();
-}
-
 static const JNINativeMethod gMethods[] = {
     /* name, signature, funcPtr */
     { "nativeCreate", "()J",
             (void*)nativeCreate },
     { "nativeDestroy", "(J)V",
             (void*)nativeDestroy },
-    { "nativeKill", "(J)V",
-            (void*)nativeKill }
 };
 
 int register_android_view_SurfaceSession(JNIEnv* env) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index dc1cc32..99014c5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1055,6 +1055,14 @@
         android:description="@string/permdesc_accessImsCallService"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi @hide Allows an application to perform IMS Single Registration related actions.
+         Only granted if the application is a system app AND is in the Default SMS Role.
+         The permission is revoked when the app is taken out of the Default SMS Role.
+        <p>Protection level: internal|role
+    -->
+    <permission android:name="android.permission.PERFORM_IMS_SINGLE_REGISTRATION"
+        android:protectionLevel="internal|role" />
+
     <!-- Allows an application to read the user's call log.
          <p class="note"><strong>Note:</strong> If your app uses the
          {@link #READ_CONTACTS} permission and <em>both</em> your <a
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index b4e580a..0ae6a76 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -311,6 +311,10 @@
         <flag name="recents" value="0x2000000" />
         <!-- Additional flag from base permission type: this permission is managed by role. -->
         <flag name="role" value="0x4000000" />
+        <!-- Additional flag from base permission type: this permission can also be granted if the
+             requesting application is signed by, or has in its signing lineage, any of the
+             certificate digests declared in {@link android.R.attr#knownCerts}. -->
+        <flag name="knownSigner" value="0x8000000" />
     </attr>
 
     <!-- Flags indicating more context for a permission group. -->
@@ -364,6 +368,15 @@
          {@link android.R.styleable#AndroidManifestPermissionGroup permission-group} tag. -->
     <attr name="permissionGroup" format="string" />
 
+    <!-- A reference to an array resource containing the signing certificate digests to be granted
+         this permission when using the {@code knownSigner} protection flag. The digest should
+         be computed over the DER encoding of the trusted certificate using the SHA-256 digest
+         algorithm.
+         <p>
+         If only a single signer is declared this can also be a string resource, or the digest
+         can be declared inline as the value for this attribute. -->
+    <attr name="knownCerts" format="reference|string" />
+
     <!-- Specify the name of a user ID that will be shared between multiple
          packages.  By default, each package gets its own unique user-id.
          By setting this value on two or more packages, each of these packages
@@ -1935,6 +1948,7 @@
         <attr name="request" />
         <attr name="protectionLevel" />
         <attr name="permissionFlags" />
+        <attr name="knownCerts" />
     </declare-styleable>
 
     <!-- The <code>permission-group</code> tag declares a logical grouping of
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index beae935..09ca12a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1946,8 +1946,6 @@
     <string name="config_systemAutomotiveProjection" translatable="false"></string>
     <!-- The name of the package that will hold the system cluster service role. -->
     <string name="config_systemAutomotiveCluster" translatable="false"></string>
-    <!-- The name of the package that will hold the system video call role. -->
-    <string name="config_systemVideoCall" translatable="false"></string>
 
     <!-- The name of the package that will be allowed to change its components' label/icon. -->
     <string name="config_overrideComponentUiPackage" translatable="false"></string>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 40c5206..068987e 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3064,6 +3064,7 @@
     <public name="previewLayout" />
     <public name="clipToOutline" />
     <public name="edgeEffectType" />
+    <public name="knownCerts" />
   </public-group>
 
   <public-group type="drawable" first-id="0x010800b5">
@@ -3118,8 +3119,6 @@
     <!-- @hide @SystemApi @TestApi -->
     <public name="config_systemAutomotiveCluster" />
     <!-- @hide @SystemApi @TestApi -->
-    <public name="config_systemVideoCall" />
-    <!-- @hide @SystemApi @TestApi -->
     <public name="config_systemAutomotiveProjection" />
   </public-group>
 
diff --git a/core/tests/coretests/src/android/app/timedetector/ExternalTimeSuggestionTest.java b/core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java
similarity index 98%
rename from core/tests/coretests/src/android/app/timedetector/ExternalTimeSuggestionTest.java
rename to core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java
index 6bcec25..266ff3d 100644
--- a/core/tests/coretests/src/android/app/timedetector/ExternalTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package android.app.time;
 
 import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
 import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
diff --git a/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java b/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
index bffd1e4..49b720c 100644
--- a/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
+++ b/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
@@ -29,6 +29,7 @@
 
 import android.content.pm.PackageParser.SigningDetails;
 import android.util.ArraySet;
+import android.util.PackageUtils;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -817,6 +818,124 @@
         assertFalse(secondDetails.hasCommonSignerWithCapability(firstDetails, PERMISSION));
     }
 
+    @Test
+    public void hasAncestorOrSelfWithDigest_nullSet_returnsFalse() throws Exception {
+        // The hasAncestorOrSelfWithDigest method is intended to verify whether the SigningDetails
+        // is currently signed, or has previously been signed, by any of the certificate digests
+        // in the provided Set. This test verifies if a null Set is provided then false is returned.
+        SigningDetails details = createSigningDetails(FIRST_SIGNATURE);
+
+        assertFalse(details.hasAncestorOrSelfWithDigest(null));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_unknownDetails_returnsFalse() throws Exception {
+        // If hasAncestorOrSelfWithDigest is invoked against an UNKNOWN
+        // instance of the SigningDetails then false is returned.
+        SigningDetails details = SigningDetails.UNKNOWN;
+        Set<String> digests = createDigestSet(FIRST_SIGNATURE);
+
+        assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_singleSignerInSet_returnsTrue() throws Exception {
+        // If the single signer of an app is in the provided digest Set then
+        // the method should return true.
+        SigningDetails details = createSigningDetails(FIRST_SIGNATURE);
+        Set<String> digests = createDigestSet(FIRST_SIGNATURE, SECOND_SIGNATURE);
+
+        assertTrue(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_singleSignerNotInSet_returnsFalse() throws Exception {
+        // If the single signer of an app is not in the provided digest Set then
+        // the method should return false.
+        SigningDetails details = createSigningDetails(FIRST_SIGNATURE);
+        Set<String> digests = createDigestSet(SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+        assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_multipleSignersInSet_returnsTrue() throws Exception {
+        // If an app is signed by multiple signers and all of the signers are in
+        // the digest Set then the method should return true.
+        SigningDetails details = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
+        Set<String> digests = createDigestSet(FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+        assertTrue(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_multipleSignersNotInSet_returnsFalse()
+            throws Exception {
+        // If an app is signed by multiple signers then all signers must be in the digest Set; if
+        // only a subset of the signers are in the Set then the method should return false.
+        SigningDetails details = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
+        Set<String> digests = createDigestSet(FIRST_SIGNATURE, THIRD_SIGNATURE);
+
+        assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_multipleSignersOneInSet_returnsFalse()
+            throws Exception {
+        // If an app is signed by multiple signers and the Set size is smaller than the number of
+        // signers then the method should immediately return false since there's no way for the
+        // requirement of all signers in the Set to be met.
+        SigningDetails details = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
+        Set<String> digests = createDigestSet(FIRST_SIGNATURE);
+
+        assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_lineageSignerInSet_returnsTrue() throws Exception {
+        // If an app has a rotated signing key and a previous key in the lineage is in the digest
+        // Set then this method should return true.
+        SigningDetails details = createSigningDetailsWithLineage(FIRST_SIGNATURE, SECOND_SIGNATURE);
+        Set<String> digests = createDigestSet(FIRST_SIGNATURE, THIRD_SIGNATURE);
+
+        assertTrue(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_lineageSignerNotInSet_returnsFalse() throws Exception {
+        // If an app has a rotated signing key, but neither the current key nor any of the signers
+        // in the lineage are in the digest set then the method should return false.
+        SigningDetails details = createSigningDetailsWithLineage(FIRST_SIGNATURE, SECOND_SIGNATURE);
+        Set<String> digests = createDigestSet(THIRD_SIGNATURE, FOURTH_SIGNATURE);
+
+        assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_lastSignerInLineageInSet_returnsTrue()
+            throws Exception {
+        // If an app has multiple signers in the lineage only one of those signers must be in the
+        // Set for this method to return true. This test verifies if the last signer in the lineage
+        // is in the set then the method returns true.
+        SigningDetails details = createSigningDetailsWithLineage(FIRST_SIGNATURE, SECOND_SIGNATURE,
+                THIRD_SIGNATURE);
+        Set<String> digests = createDigestSet(SECOND_SIGNATURE);
+
+        assertTrue(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_nullLineageSingleSIgner_returnsFalse()
+            throws Exception {
+        // Under some instances an app with only a single signer can have a null lineage; this
+        // test verifies that null lineage does not result in a NullPointerException and instead the
+        // method returns false if the single signer is not in the Set.
+        SigningDetails details = createSigningDetails(true, FIRST_SIGNATURE);
+        Set<String> digests = createDigestSet(SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+        assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
     private SigningDetails createSigningDetailsWithLineage(String... signers) throws Exception {
         int[] capabilities = new int[signers.length];
         for (int i = 0; i < capabilities.length; i++) {
@@ -853,12 +972,21 @@
         // If there are multiple signers then the pastSigningCertificates should be set to null, but
         // if there is only a single signer both the current signer and the past signers should be
         // set to that one signer.
-        if (signers.length > 1) {
+        if (signers.length > 1 || useNullPastSigners) {
             return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, null);
         }
         return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, currentSignatures);
     }
 
+    private Set<String> createDigestSet(String... signers) {
+        Set<String> digests = new ArraySet<>();
+        for (String signer : signers) {
+            String digest = PackageUtils.computeSha256Digest(new Signature(signer).toByteArray());
+            digests.add(digest);
+        }
+        return digests;
+    }
+
     private void assertSigningDetailsContainsLineage(SigningDetails details,
             String... pastSigners) {
         // This method should only be invoked for results that contain a single signer.
diff --git a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
index 564103e..11239db 100644
--- a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
@@ -17,6 +17,8 @@
 package android.os;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
 
 import static org.testng.Assert.assertThrows;
 
@@ -31,7 +33,6 @@
 @Presubmit
 @RunWith(JUnit4.class)
 public class CombinedVibrationEffectTest {
-
     private static final VibrationEffect VALID_EFFECT = VibrationEffect.createOneShot(10, 255);
     private static final VibrationEffect INVALID_EFFECT = new VibrationEffect.OneShot(-1, -1);
 
@@ -172,6 +173,37 @@
     }
 
     @Test
+    public void testHasVibratorMono_returnsTrueForAnyVibrator() {
+        CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(
+                VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
+        assertTrue(effect.hasVibrator(0));
+        assertTrue(effect.hasVibrator(1));
+    }
+
+    @Test
+    public void testHasVibratorStereo_returnsOnlyTheIdsSet() {
+        CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced()
+                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .combine();
+        assertFalse(effect.hasVibrator(0));
+        assertTrue(effect.hasVibrator(1));
+        assertFalse(effect.hasVibrator(2));
+    }
+
+    @Test
+    public void testHasVibratorSequential_returnsNestedVibrators() {
+        CombinedVibrationEffect effect = CombinedVibrationEffect.startSequential()
+                .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addNext(CombinedVibrationEffect.startSynced()
+                        .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
+                        .combine())
+                .combine();
+        assertFalse(effect.hasVibrator(0));
+        assertTrue(effect.hasVibrator(1));
+        assertTrue(effect.hasVibrator(2));
+    }
+
+    @Test
     public void testSerializationMono() {
         CombinedVibrationEffect original = CombinedVibrationEffect.createSynced(VALID_EFFECT);
 
diff --git a/core/tests/coretests/src/android/provider/OWNERS b/core/tests/coretests/src/android/provider/OWNERS
new file mode 100644
index 0000000..581da71
--- /dev/null
+++ b/core/tests/coretests/src/android/provider/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/provider/OWNERS
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index 97c07ea..6652c64 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -24,6 +24,7 @@
 import android.os.BatteryStats.HistoryItem;
 import android.os.BatteryStats.Uid.Sensor;
 import android.os.WorkSource;
+import android.util.SparseLongArray;
 import android.view.Display;
 
 import androidx.test.filters.SmallTest;
@@ -583,6 +584,95 @@
         checkMeasuredEnergy("H", uid1, blame1, uid2, blame2, globalDoze, bi);
     }
 
+    @SmallTest
+    public void testUpdateCustomMeasuredEnergyDataLocked_neverCalled() {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        bi.setOnBatteryInternal(true);
+
+        final int uid1 = 11500;
+        final int uid2 = 11501;
+
+        // Initially, all custom buckets report energy of 0.
+        checkCustomMeasuredEnergy("0", 0, 0, uid1, 0, 0, uid2, 0, 0, bi);
+    }
+
+    @SmallTest
+    public void testUpdateCustomMeasuredEnergyDataLocked() {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+
+        final int bucketA = 0; // Custom bucket 0
+        final int bucketB = 1; // Custom bucket 1
+
+        long totalBlameA = 0; // Total energy consumption for bucketA (may exceed sum of uids)
+        long totalBlameB = 0; // Total energy consumption for bucketB (may exceed sum of uids)
+
+        final int uid1 = 10500;
+        long blame1A = 0; // Blame for uid1 in bucketA
+        long blame1B = 0; // Blame for uid1 in bucketB
+
+        final int uid2 = 10501;
+        long blame2A = 0; // Blame for uid2 in bucketA
+        long blame2B = 0; // Blame for uid2 in bucketB
+
+        final SparseLongArray newEnergiesA = new SparseLongArray(2);
+        final SparseLongArray newEnergiesB = new SparseLongArray(2);
+
+
+        // ----- Case A: battery off (so blame does not increase)
+        bi.setOnBatteryInternal(false);
+
+        newEnergiesA.put(uid1, 20_000);
+        // Implicit newEnergiesA.put(uid2, 0);
+        bi.updateCustomMeasuredEnergyDataLocked(bucketA, 500_000, newEnergiesA);
+
+        newEnergiesB.put(uid1, 60_000);
+        // Implicit newEnergiesB.put(uid2, 0);
+        bi.updateCustomMeasuredEnergyDataLocked(bucketB, 700_000, newEnergiesB);
+
+        checkCustomMeasuredEnergy(
+                "A", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+
+
+        // ----- Case B: battery on
+        bi.setOnBatteryInternal(true);
+
+        newEnergiesA.put(uid1, 7_000); blame1A += 7_000;
+        // Implicit newEnergiesA.put(uid2, 0); blame2A += 0;
+        bi.updateCustomMeasuredEnergyDataLocked(bucketA, 310_000, newEnergiesA);
+        totalBlameA += 310_000;
+
+        newEnergiesB.put(uid1, 63_000); blame1B += 63_000;
+        newEnergiesB.put(uid2, 15_000); blame2B += 15_000;
+        bi.updateCustomMeasuredEnergyDataLocked(bucketB, 790_000, newEnergiesB);
+        totalBlameB += 790_000;
+
+        checkCustomMeasuredEnergy(
+                "B", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+
+
+        // ----- Case C: battery still on
+        newEnergiesA.delete(uid1); blame1A += 0;
+        newEnergiesA.put(uid2, 16_000); blame2A += 16_000;
+        bi.updateCustomMeasuredEnergyDataLocked(bucketA, 560_000, newEnergiesA);
+        totalBlameA += 560_000;
+
+        bi.updateCustomMeasuredEnergyDataLocked(bucketB, 10_000, null);
+        totalBlameB += 10_000;
+
+        checkCustomMeasuredEnergy(
+                "C", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+
+
+        // ----- Case D: battery still on
+        bi.updateCustomMeasuredEnergyDataLocked(bucketA, 0, newEnergiesA);
+        bi.updateCustomMeasuredEnergyDataLocked(bucketB, 15_000, new SparseLongArray(1));
+        totalBlameB += 15_000;
+        checkCustomMeasuredEnergy(
+                "D", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+    }
+
     private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) {
         // Note that noteUidProcessStateLocked uses ActivityManager process states.
         if (fgOn) {
@@ -610,4 +700,33 @@
         assertEquals("Wrong doze for Case " + caseName, globalDoze,
                 bi.getScreenDozeEnergy());
     }
+
+    private void checkCustomMeasuredEnergy(String caseName,
+            long totalBlameA, long totalBlameB,
+            int uid1, long blame1A, long blame1B,
+            int uid2, long blame2A, long blame2B,
+            MockBatteryStatsImpl bi) {
+
+        final long[] actualTotal = bi.getCustomMeasuredEnergiesMicroJoules();
+        final long[] actualUid1 = bi.getUidStatsLocked(uid1).getCustomMeasuredEnergiesMicroJoules();
+        final long[] actualUid2 = bi.getUidStatsLocked(uid2).getCustomMeasuredEnergiesMicroJoules();
+
+        assertNotNull(actualTotal);
+        assertNotNull(actualUid1);
+        assertNotNull(actualUid2);
+
+        assertEquals("Wrong total blame in bucket 0 for Case " + caseName, totalBlameA,
+                actualTotal[0]);
+
+        assertEquals("Wrong total blame in bucket 1 for Case " + caseName, totalBlameB,
+                actualTotal[1]);
+
+        assertEquals("Wrong uid1 blame in bucket 0 for Case " + caseName, blame1A, actualUid1[0]);
+
+        assertEquals("Wrong uid1 blame in bucket 1 for Case " + caseName, blame1B, actualUid1[1]);
+
+        assertEquals("Wrong uid2 blame in bucket 0 for Case " + caseName, blame2A, actualUid2[0]);
+
+        assertEquals("Wrong uid2 blame in bucket 1 for Case " + caseName, blame2B, actualUid2[1]);
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
index b9908f4..5fd5a78 100644
--- a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
@@ -387,6 +387,59 @@
     }
 
     @Test
+    public void testIsValidCustomBucket() {
+        final MeasuredEnergyStats stats
+                = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 3);
+        assertFalse(stats.isValidCustomBucket(-1));
+        assertTrue(stats.isValidCustomBucket(0));
+        assertTrue(stats.isValidCustomBucket(1));
+        assertTrue(stats.isValidCustomBucket(2));
+        assertFalse(stats.isValidCustomBucket(3));
+        assertFalse(stats.isValidCustomBucket(4));
+
+        final MeasuredEnergyStats boringStats
+                = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 0);
+        assertFalse(boringStats.isValidCustomBucket(-1));
+        assertFalse(boringStats.isValidCustomBucket(0));
+        assertFalse(boringStats.isValidCustomBucket(1));
+    }
+
+    @Test
+    public void testGetAccumulatedCustomBucketEnergies() {
+        final MeasuredEnergyStats stats
+                = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 3);
+
+        stats.updateCustomBucket(0, 50, true);
+        stats.updateCustomBucket(1, 60, true);
+        stats.updateCustomBucket(2, 13, true);
+        stats.updateCustomBucket(1, 70, true);
+
+        final long[] output = stats.getAccumulatedCustomBucketEnergies();
+        assertEquals(3, output.length);
+
+        assertEquals(50, output[0]);
+        assertEquals(60 + 70, output[1]);
+        assertEquals(13, output[2]);
+    }
+
+    @Test
+    public void testGetAccumulatedCustomBucketEnergies_empty() {
+        final MeasuredEnergyStats stats
+                = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 0);
+
+        final long[] output = stats.getAccumulatedCustomBucketEnergies();
+        assertEquals(0, output.length);
+    }
+
+    @Test
+    public void testGetNumberCustomEnergyBuckets() {
+        assertEquals(0, new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 0)
+                .getNumberCustomEnergyBuckets());
+        assertEquals(3, new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 3)
+                .getNumberCustomEnergyBuckets());
+    }
+
+    @Test
     public void testReset() {
         final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
         final int numCustomBuckets = 2;
diff --git a/data/etc/car/com.android.car.developeroptions.xml b/data/etc/car/com.android.car.developeroptions.xml
index cf0199b..c940574 100644
--- a/data/etc/car/com.android.car.developeroptions.xml
+++ b/data/etc/car/com.android.car.developeroptions.xml
@@ -26,6 +26,7 @@
         <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS"/>
         <permission name="android.permission.DELETE_PACKAGES"/>
         <permission name="android.permission.FORCE_STOP_PACKAGES"/>
+        <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
         <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
         <permission name="android.permission.MANAGE_DEBUGGING"/>
         <permission name="android.permission.MANAGE_FINGERPRINT"/>
@@ -40,10 +41,12 @@
         <permission name="android.permission.MOVE_PACKAGE"/>
         <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
         <permission name="android.permission.PACKAGE_USAGE_STATS"/>
+        <permission name="android.permission.READ_DREAM_STATE"/>
         <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
         <permission name="android.permission.READ_SEARCH_INDEXABLES"/>
         <permission name="android.permission.REBOOT"/>
         <permission name="android.permission.REQUEST_NETWORK_SCORES"/>
+        <permission name="android.permission.RESTART_WIFI_SUBSYSTEM"/>
         <permission name="android.permission.SET_TIME"/>
         <permission name="android.permission.STATUS_BAR"/>
         <permission name="android.permission.TETHER_PRIVILEGED"/>
diff --git a/data/etc/car/com.android.car.xml b/data/etc/car/com.android.car.xml
index 19548bc..48f6ab3 100644
--- a/data/etc/car/com.android.car.xml
+++ b/data/etc/car/com.android.car.xml
@@ -25,6 +25,7 @@
         <permission name="android.permission.REAL_GET_TASKS"/>
         <permission name="android.permission.REBOOT"/>
         <permission name="android.permission.READ_LOGS"/>
+        <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/car/com.google.android.car.kitchensink.xml b/data/etc/car/com.google.android.car.kitchensink.xml
index 3a20a9c..bd30d7a 100644
--- a/data/etc/car/com.google.android.car.kitchensink.xml
+++ b/data/etc/car/com.google.android.car.kitchensink.xml
@@ -27,8 +27,10 @@
         <permission name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
         <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
         <permission name="android.permission.LOCATION_HARDWARE"/>
+        <permission name="android.permission.LOCK_DEVICE"/>
         <permission name="android.permission.MANAGE_USB"/>
         <permission name="android.permission.MANAGE_USERS"/>
+        <permission name="android.permission.MASTER_CLEAR"/>
         <!-- use for CarServiceTest -->
         <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
         <permission name="android.permission.MODIFY_AUDIO_ROUTING"/>
@@ -40,9 +42,11 @@
         <permission name="android.permission.REAL_GET_TASKS"/>
         <permission name="android.permission.READ_LOGS"/>
         <permission name="android.permission.REBOOT"/>
+        <permission name="android.permission.RESET_PASSWORD"/>
         <permission name="android.permission.SEND_CATEGORY_CAR_NOTIFICATIONS"/>
         <!-- use for CarServiceTest -->
         <permission name="android.permission.SET_ACTIVITY_WATCHER"/>
+        <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
 
         <!-- use for rotary fragment to enable/disable packages related to rotary -->
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 35e6b859..40c75a4 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1414,6 +1414,16 @@
         return Arrays.binarySearch(mSupportedAxes, axis) >= 0;
     }
 
+    /** @hide */
+    public List<FontFamily> getFallback() {
+        ArrayList<FontFamily> families = new ArrayList<>();
+        int familySize = nativeGetFamilySize(native_instance);
+        for (int i = 0; i < familySize; ++i) {
+            families.add(new FontFamily(nativeGetFamily(native_instance, i)));
+        }
+        return families;
+    }
+
     private static native long nativeCreateFromTypeface(long native_instance, int style);
     private static native long nativeCreateFromTypefaceWithExactStyle(
             long native_instance, int weight, boolean italic);
@@ -1439,6 +1449,13 @@
     @CriticalNative
     private static native long nativeGetReleaseFunc();
 
+    @CriticalNative
+    private static native int nativeGetFamilySize(long naitvePtr);
+
+    @CriticalNative
+    private static native long nativeGetFamily(long nativePtr, int index);
+
+
     private static native void nativeRegisterGenericFamily(String str, long nativePtr);
 
     private static native int nativeWriteTypefaces(
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index 8c13d3e..a771a6e 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -125,7 +125,7 @@
                 nAddFont(builderPtr, mFonts.get(i).getNativePtr());
             }
             final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback);
-            final FontFamily family = new FontFamily(mFonts, ptr);
+            final FontFamily family = new FontFamily(ptr);
             sFamilyRegistory.registerNativeAllocation(family, ptr);
             return family;
         }
@@ -146,7 +146,8 @@
     private final long mNativePtr;
 
     // Use Builder instead.
-    private FontFamily(@NonNull ArrayList<Font> fonts, long ptr) {
+    /** @hide */
+    public FontFamily(long ptr) {
         mNativePtr = ptr;
     }
 
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 904085f..255f9e6 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -68,44 +68,23 @@
      */
     public static @NonNull Set<Font> getAvailableFonts() {
         synchronized (LOCK) {
-            if (sAvailableFonts != null) {
-                return sAvailableFonts;
-            }
-
-            if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
-                sAvailableFonts = collectAllFonts();
-            } else {
+            if (sAvailableFonts == null) {
                 Set<Font> set = new ArraySet<>();
-                for (FontFamily[] items : sFamilyMap.values()) {
-                    for (FontFamily family : items) {
-                        for (int i = 0; i < family.getSize(); ++i) {
-                            set.add(family.getFont(i));
+                for (Typeface tf : Typeface.getSystemFontMap().values()) {
+                    List<FontFamily> families = tf.getFallback();
+                    for (int i = 0; i < families.size(); ++i) {
+                        FontFamily family = families.get(i);
+                        for (int j = 0; j < family.getSize(); ++j) {
+                            set.add(family.getFont(j));
                         }
                     }
                 }
-
                 sAvailableFonts = Collections.unmodifiableSet(set);
             }
             return sAvailableFonts;
         }
     }
 
-    private static @NonNull Set<Font> collectAllFonts() {
-        // TODO: use updated fonts
-        FontConfig fontConfig = getSystemPreinstalledFontConfig();
-        Map<String, FontFamily[]> map = buildSystemFallback(fontConfig);
-
-        Set<Font> res = new ArraySet<>();
-        for (FontFamily[] families : map.values()) {
-            for (FontFamily family : families) {
-                for (int i = 0; i < family.getSize(); ++i) {
-                    res.add(family.getFont(i));
-                }
-            }
-        }
-        return res;
-    }
-
     private static @Nullable ByteBuffer mmap(@NonNull String fullPath) {
         try (FileInputStream file = new FileInputStream(fullPath)) {
             final FileChannel fileChannel = file.getChannel();
@@ -329,13 +308,4 @@
         Typeface.initSystemDefaultTypefaces(fallbackMap, fontConfig.getAliases(), result);
         return result;
     }
-
-    /**
-     * @hide
-     */
-    public void resetFallbackMapping(Map<String, FontFamily[]> fallbackMap) {
-        synchronized (LOCK) {
-            sFamilyMap = fallbackMap;
-        }
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
index aa82339..73fd693 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
@@ -16,24 +16,16 @@
 
 package com.android.wm.shell;
 
-import android.util.Slog;
-
-import com.android.wm.shell.apppairs.AppPairs;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.onehanded.OneHanded;
-import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.common.annotations.ExternalThread;
 
 import java.io.PrintWriter;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
 
 /**
  * An entry point into the shell for dumping shell internal state and running adb commands.
  *
  * Use with {@code adb shell dumpsys activity service SystemUIService WMShell ...}.
  */
+@ExternalThread
 public interface ShellCommandHandler {
     /**
      * Dumps the shell state.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
index 982cc00..eaed24d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
@@ -18,13 +18,12 @@
 
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
 
-import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.apppairs.AppPairsController;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
-import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
+import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import java.io.PrintWriter;
@@ -38,24 +37,24 @@
 public final class ShellCommandHandlerImpl {
     private static final String TAG = ShellCommandHandlerImpl.class.getSimpleName();
 
-    private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
+    private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional;
     private final Optional<SplitScreenController> mSplitScreenOptional;
     private final Optional<Pip> mPipOptional;
-    private final Optional<OneHanded> mOneHandedOptional;
-    private final Optional<HideDisplayCutout> mHideDisplayCutout;
+    private final Optional<OneHandedController> mOneHandedOptional;
+    private final Optional<HideDisplayCutoutController> mHideDisplayCutout;
+    private final Optional<AppPairsController> mAppPairsOptional;
     private final ShellTaskOrganizer mShellTaskOrganizer;
-    private final Optional<AppPairs> mAppPairsOptional;
     private final ShellExecutor mMainExecutor;
     private final HandlerImpl mImpl = new HandlerImpl();
 
     public static ShellCommandHandler create(
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
             Optional<SplitScreenController> splitScreenOptional,
             Optional<Pip> pipOptional,
-            Optional<OneHanded> oneHandedOptional,
-            Optional<HideDisplayCutout> hideDisplayCutout,
-            Optional<AppPairs> appPairsOptional,
+            Optional<OneHandedController> oneHandedOptional,
+            Optional<HideDisplayCutoutController> hideDisplayCutout,
+            Optional<AppPairsController> appPairsOptional,
             ShellExecutor mainExecutor) {
         return new ShellCommandHandlerImpl(shellTaskOrganizer, legacySplitScreenOptional,
                 splitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout,
@@ -64,12 +63,12 @@
 
     private ShellCommandHandlerImpl(
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
             Optional<SplitScreenController> splitScreenOptional,
             Optional<Pip> pipOptional,
-            Optional<OneHanded> oneHandedOptional,
-            Optional<HideDisplayCutout> hideDisplayCutout,
-            Optional<AppPairs> appPairsOptional,
+            Optional<OneHandedController> oneHandedOptional,
+            Optional<HideDisplayCutoutController> hideDisplayCutout,
+            Optional<AppPairsController> appPairsOptional,
             ShellExecutor mainExecutor) {
         mShellTaskOrganizer = shellTaskOrganizer;
         mLegacySplitScreenOptional = legacySplitScreenOptional;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
index 925bf4b..7376d98 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
@@ -18,13 +18,12 @@
 
 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
 
-import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.apppairs.AppPairsController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.transition.Transitions;
 
@@ -39,9 +38,9 @@
     private final DisplayImeController mDisplayImeController;
     private final DragAndDropController mDragAndDropController;
     private final ShellTaskOrganizer mShellTaskOrganizer;
-    private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
+    private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional;
     private final Optional<SplitScreenController> mSplitScreenOptional;
-    private final Optional<AppPairs> mAppPairsOptional;
+    private final Optional<AppPairsController> mAppPairsOptional;
     private final FullscreenTaskListener mFullscreenTaskListener;
     private final ShellExecutor mMainExecutor;
     private final Transitions mTransitions;
@@ -51,9 +50,9 @@
     public static ShellInit create(DisplayImeController displayImeController,
             DragAndDropController dragAndDropController,
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
             Optional<SplitScreenController> splitScreenOptional,
-            Optional<AppPairs> appPairsOptional,
+            Optional<AppPairsController> appPairsOptional,
             FullscreenTaskListener fullscreenTaskListener,
             Transitions transitions,
             ShellExecutor mainExecutor) {
@@ -71,9 +70,9 @@
     private ShellInitImpl(DisplayImeController displayImeController,
             DragAndDropController dragAndDropController,
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
             Optional<SplitScreenController> splitScreenOptional,
-            Optional<AppPairs> appPairsOptional,
+            Optional<AppPairsController> appPairsOptional,
             FullscreenTaskListener fullscreenTaskListener,
             Transitions transitions,
             ShellExecutor mainExecutor) {
@@ -97,7 +96,7 @@
         // Register the shell organizer
         mShellTaskOrganizer.registerOrganizer();
 
-        mAppPairsOptional.ifPresent(AppPairs::onOrganizerRegistered);
+        mAppPairsOptional.ifPresent(AppPairsController::onOrganizerRegistered);
         mSplitScreenOptional.ifPresent(SplitScreenController::onOrganizerRegistered);
 
         // Bind the splitscreen impl to the drag drop controller
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index a570c0a..b22f358 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -44,7 +44,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
+import com.android.wm.shell.sizecompatui.SizeCompatUIController;
 import com.android.wm.shell.startingsurface.StartingSurfaceDrawer;
 
 import java.io.PrintWriter;
@@ -108,20 +108,20 @@
      * compat.
      */
     @Nullable
-    private final SizeCompatUI mSizeCompatUI;
+    private final SizeCompatUIController mSizeCompatUI;
 
     public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) {
         this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */);
     }
 
     public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
-            SizeCompatUI sizeCompatUI) {
+            SizeCompatUIController sizeCompatUI) {
         this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI);
     }
 
     @VisibleForTesting
     ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor,
-            Context context, @Nullable SizeCompatUI sizeCompatUI) {
+            Context context, @Nullable SizeCompatUIController sizeCompatUI) {
         super(taskOrganizerController, mainExecutor);
         // TODO(b/131727939) temporarily live here, the starting surface drawer should be controlled
         //  by a controller, that class should be create while porting
@@ -342,8 +342,8 @@
     }
 
     /**
-     * Notifies {@link SizeCompatUI} about the size compat info changed on the give Task to update
-     * the UI accordingly.
+     * Notifies {@link SizeCompatUIController} about the size compat info changed on the give Task
+     * to update the UI accordingly.
      *
      * @param taskInfo the new Task info
      * @param taskListener listener to handle the Task Surface placement. {@code null} if task is
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
index a5dd79b..58ca1fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
@@ -30,6 +30,7 @@
 public class TaskViewFactoryController {
     private final ShellTaskOrganizer mTaskOrganizer;
     private final ShellExecutor mShellExecutor;
+    private final TaskViewFactory mImpl = new TaskViewFactoryImpl();
 
     public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer,
             ShellExecutor shellExecutor) {
@@ -37,8 +38,11 @@
         mShellExecutor = shellExecutor;
     }
 
+    public TaskViewFactory asTaskViewFactory() {
+        return mImpl;
+    }
+
     /** Creates an {@link TaskView} */
-    @ShellMainThread
     public void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate) {
         TaskView taskView = new TaskView(context, mTaskOrganizer);
         executor.execute(() -> {
@@ -46,10 +50,6 @@
         });
     }
 
-    public TaskViewFactory getTaskViewFactory() {
-        return new TaskViewFactoryImpl();
-    }
-
     private class TaskViewFactoryImpl implements TaskViewFactory {
         @ExternalThread
         public void create(@UiContext Context context,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
index abd9257..59271e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
@@ -27,6 +27,7 @@
 /**
  * The singleton wrapper to communicate between WindowManagerService and WMShell features
  * (e.g: PIP, SplitScreen, Bubble, OneHandedMode...etc)
+ * TODO: Remove once PinnedStackListenerForwarder can be removed
  */
 public class WindowManagerShellWrapper {
     private static final String TAG = WindowManagerShellWrapper.class.getSimpleName();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
index f5aa852..a9b1dbc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
@@ -35,8 +35,4 @@
     boolean pair(ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2);
     /** Unpairs any app-pair containing this task id. */
     void unpair(int taskId);
-    /** Dumps current status of app pairs. */
-    void dump(@NonNull PrintWriter pw, String prefix);
-    /** Called when the shell organizer has been registered. */
-    void onOrganizerRegistered();
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
index e380426..0415f12 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
@@ -51,18 +51,7 @@
     private final SparseArray<AppPair> mActiveAppPairs = new SparseArray<>();
     private final DisplayController mDisplayController;
 
-    /**
-     * Creates {@link AppPairs}, returns {@code null} if the feature is not supported.
-     */
-    @Nullable
-    public static AppPairs create(ShellTaskOrganizer organizer,
-            SyncTransactionQueue syncQueue, DisplayController displayController,
-            ShellExecutor mainExecutor) {
-        return new AppPairsController(organizer, syncQueue, displayController,
-                mainExecutor).mImpl;
-    }
-
-    AppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
+    public AppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
                 DisplayController displayController, ShellExecutor mainExecutor) {
         mTaskOrganizer = organizer;
         mSyncQueue = syncQueue;
@@ -70,18 +59,22 @@
         mMainExecutor = mainExecutor;
     }
 
-    void onOrganizerRegistered() {
+    public AppPairs asAppPairs() {
+        return mImpl;
+    }
+
+    public void onOrganizerRegistered() {
         if (mPairsPool == null) {
             setPairsPool(new AppPairsPool(this));
         }
     }
 
     @VisibleForTesting
-    void setPairsPool(AppPairsPool pool) {
+    public void setPairsPool(AppPairsPool pool) {
         mPairsPool = pool;
     }
 
-    boolean pair(int taskId1, int taskId2) {
+    public boolean pair(int taskId1, int taskId2) {
         final ActivityManager.RunningTaskInfo task1 = mTaskOrganizer.getRunningTaskInfo(taskId1);
         final ActivityManager.RunningTaskInfo task2 = mTaskOrganizer.getRunningTaskInfo(taskId2);
         if (task1 == null || task2 == null) {
@@ -90,13 +83,13 @@
         return pair(task1, task2);
     }
 
-    boolean pair(ActivityManager.RunningTaskInfo task1,
+    public boolean pair(ActivityManager.RunningTaskInfo task1,
             ActivityManager.RunningTaskInfo task2) {
         return pairInner(task1, task2) != null;
     }
 
     @VisibleForTesting
-    AppPair pairInner(
+    public AppPair pairInner(
             @NonNull ActivityManager.RunningTaskInfo task1,
             @NonNull ActivityManager.RunningTaskInfo task2) {
         final AppPair pair = mPairsPool.acquire();
@@ -109,11 +102,11 @@
         return pair;
     }
 
-    void unpair(int taskId) {
+    public void unpair(int taskId) {
         unpair(taskId, true /* releaseToPool */);
     }
 
-    void unpair(int taskId, boolean releaseToPool) {
+    public void unpair(int taskId, boolean releaseToPool) {
         AppPair pair = mActiveAppPairs.get(taskId);
         if (pair == null) {
             for (int i = mActiveAppPairs.size() - 1; i >= 0; --i) {
@@ -137,19 +130,19 @@
         }
     }
 
-    ShellTaskOrganizer getTaskOrganizer() {
+    public ShellTaskOrganizer getTaskOrganizer() {
         return mTaskOrganizer;
     }
 
-    SyncTransactionQueue getSyncTransactionQueue() {
+    public SyncTransactionQueue getSyncTransactionQueue() {
         return mSyncQueue;
     }
 
-    DisplayController getDisplayController() {
+    public DisplayController getDisplayController() {
         return mDisplayController;
     }
 
-    private void dump(@NonNull PrintWriter pw, String prefix) {
+    public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         final String childPrefix = innerPrefix + "  ";
         pw.println(prefix + this);
@@ -202,21 +195,5 @@
                 AppPairsController.this.unpair(taskId);
             });
         }
-
-        @Override
-        public void onOrganizerRegistered() {
-            mMainExecutor.execute(() -> {
-                AppPairsController.this.onOrganizerRegistered();
-            });
-        }
-
-        @Override
-        public void dump(@NonNull PrintWriter pw, String prefix) {
-            try {
-                mMainExecutor.executeBlocking(() -> AppPairsController.this.dump(pw, prefix));
-            } catch (InterruptedException e) {
-                Slog.e(TAG, "Failed to dump AppPairsController in 2s");
-            }
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d73fc6d..2391a08 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -192,9 +192,9 @@
     private boolean mIsStatusBarShade = true;
 
     /**
-     * Injected constructor.
+     * Creates an instance of the BubbleController.
      */
-    public static Bubbles create(Context context,
+    public static BubbleController create(Context context,
             @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
             FloatingContentCoordinator floatingContentCoordinator,
             @Nullable IStatusBarService statusBarService,
@@ -211,14 +211,14 @@
         return new BubbleController(context, data, synchronizer, floatingContentCoordinator,
                 new BubbleDataRepository(context, launcherApps, mainExecutor),
                 statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
-                logger, organizer, positioner, mainExecutor, mainHandler).mImpl;
+                logger, organizer, positioner, mainExecutor, mainHandler);
     }
 
     /**
      * Testing constructor.
      */
     @VisibleForTesting
-    public BubbleController(Context context,
+    protected BubbleController(Context context,
             BubbleData data,
             @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
             FloatingContentCoordinator floatingContentCoordinator,
@@ -322,7 +322,7 @@
     }
 
     @VisibleForTesting
-    public Bubbles getImpl() {
+    public Bubbles asBubbles() {
         return mImpl;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 2100428..2f31acd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -163,6 +163,7 @@
                         // Apply flags to make behaviour match documentLaunchMode=always.
                         fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
                         fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                        fillInIntent.putExtra(Intent.EXTRA_IS_BUBBLED, true);
                         if (mBubble != null) {
                             mBubble.setIntentActive();
                         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
index 3a2f0da..60123ab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
@@ -31,12 +31,6 @@
 public interface HideDisplayCutout {
     /**
      * Notifies {@link Configuration} changed.
-     * @param newConfig
      */
     void onConfigurationChanged(Configuration newConfig);
-
-    /**
-     * Dumps hide display cutout status.
-     */
-    void dump(@NonNull PrintWriter pw);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
index 12b8b87..23f76ca5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
@@ -44,20 +44,12 @@
     @VisibleForTesting
     boolean mEnabled;
 
-    HideDisplayCutoutController(Context context, HideDisplayCutoutOrganizer organizer,
-            ShellExecutor mainExecutor) {
-        mContext = context;
-        mOrganizer = organizer;
-        mMainExecutor = mainExecutor;
-        updateStatus();
-    }
-
     /**
      * Creates {@link HideDisplayCutoutController}, returns {@code null} if the feature is not
      * supported.
      */
     @Nullable
-    public static HideDisplayCutout create(
+    public static HideDisplayCutoutController create(
             Context context, DisplayController displayController, ShellExecutor mainExecutor) {
         // The SystemProperty is set for devices that support this feature and is used to control
         // whether to create the HideDisplayCutout instance.
@@ -68,7 +60,19 @@
 
         HideDisplayCutoutOrganizer organizer =
                 new HideDisplayCutoutOrganizer(context, displayController, mainExecutor);
-        return new HideDisplayCutoutController(context, organizer, mainExecutor).mImpl;
+        return new HideDisplayCutoutController(context, organizer, mainExecutor);
+    }
+
+    HideDisplayCutoutController(Context context, HideDisplayCutoutOrganizer organizer,
+            ShellExecutor mainExecutor) {
+        mContext = context;
+        mOrganizer = organizer;
+        mMainExecutor = mainExecutor;
+        updateStatus();
+    }
+
+    public HideDisplayCutout asHideDisplayCutout() {
+        return mImpl;
     }
 
     @VisibleForTesting
@@ -94,7 +98,7 @@
         updateStatus();
     }
 
-    private void dump(@NonNull PrintWriter pw) {
+    public void dump(@NonNull PrintWriter pw) {
         final String prefix = "  ";
         pw.print(TAG);
         pw.println(" states: ");
@@ -111,14 +115,5 @@
                 HideDisplayCutoutController.this.onConfigurationChanged(newConfig);
             });
         }
-
-        @Override
-        public void dump(@NonNull PrintWriter pw) {
-            try {
-                mMainExecutor.executeBlocking(() -> HideDisplayCutoutController.this.dump(pw));
-            } catch (InterruptedException e) {
-                Slog.e(TAG, "Failed to dump HideDisplayCutoutController in 2s");
-            }
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
index bca6deb..d25bef1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
@@ -115,21 +115,6 @@
     private volatile boolean mAdjustedForIme = false;
     private boolean mHomeStackResizable = false;
 
-    /**
-     * Creates {@link SplitScreen}, returns {@code null} if the feature is not supported.
-     */
-    @Nullable
-    public static LegacySplitScreen create(Context context,
-            DisplayController displayController, SystemWindows systemWindows,
-            DisplayImeController imeController, TransactionPool transactionPool,
-            ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
-            TaskStackListenerImpl taskStackListener, Transitions transitions,
-            ShellExecutor mainExecutor, AnimationHandler sfVsyncAnimationHandler) {
-        return new LegacySplitScreenController(context, displayController, systemWindows,
-                imeController, transactionPool, shellTaskOrganizer, syncQueue, taskStackListener,
-                transitions, mainExecutor, sfVsyncAnimationHandler).mImpl;
-    }
-
     public LegacySplitScreenController(Context context,
             DisplayController displayController, SystemWindows systemWindows,
             DisplayImeController imeController, TransactionPool transactionPool,
@@ -228,8 +213,12 @@
                     }
                 });
     }
+    
+    public LegacySplitScreen asLegacySplitScreen() {
+        return mImpl;
+    }
 
-    void onSplitScreenSupported() {
+    public void onSplitScreenSupported() {
         // Set starting tile bounds based on middle target
         final WindowContainerTransaction tct = new WindowContainerTransaction();
         int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
@@ -237,7 +226,7 @@
         mTaskOrganizer.applyTransaction(tct);
     }
 
-    private void onKeyguardVisibilityChanged(boolean showing) {
+    public void onKeyguardVisibilityChanged(boolean showing) {
         if (!isSplitActive() || mView == null) {
             return;
         }
@@ -293,19 +282,19 @@
         }
     }
 
-    boolean isMinimized() {
+    public boolean isMinimized() {
         return mMinimized;
     }
 
-    boolean isHomeStackResizable() {
+    public boolean isHomeStackResizable() {
         return mHomeStackResizable;
     }
 
-    DividerView getDividerView() {
+    public DividerView getDividerView() {
         return mView;
     }
 
-    boolean isDividerVisible() {
+    public boolean isDividerVisible() {
         return mView != null && mView.getVisibility() == View.VISIBLE;
     }
 
@@ -314,13 +303,13 @@
      * isDividerVisible because the divider is only visible once *everything* is in split mode
      * while this only cares if some things are (eg. while entering/exiting as well).
      */
-    private boolean isSplitActive() {
+    public boolean isSplitActive() {
         return mSplits.mPrimary != null && mSplits.mSecondary != null
                 && (mSplits.mPrimary.topActivityType != ACTIVITY_TYPE_UNDEFINED
                 || mSplits.mSecondary.topActivityType != ACTIVITY_TYPE_UNDEFINED);
     }
 
-    private void addDivider(Configuration configuration) {
+    public void addDivider(Configuration configuration) {
         Context dctx = mDisplayController.getDisplayContext(mContext.getDisplayId());
         mView = (DividerView)
                 LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null);
@@ -338,14 +327,14 @@
         mWindowManager.add(mView, width, height, mContext.getDisplayId());
     }
 
-    private void removeDivider() {
+    public void removeDivider() {
         if (mView != null) {
             mView.onDividerRemoved();
         }
         mWindowManager.remove();
     }
 
-    private void update(Configuration configuration) {
+    public void update(Configuration configuration) {
         final boolean isDividerHidden = mView != null && mIsKeyguardShowing;
 
         removeDivider();
@@ -358,11 +347,11 @@
         mView.setHidden(isDividerHidden);
     }
 
-    void onTaskVanished() {
+    public void onTaskVanished() {
         removeDivider();
     }
 
-    private void updateVisibility(final boolean visible) {
+    public void updateVisibility(final boolean visible) {
         if (DEBUG) Slog.d(TAG, "Updating visibility " + mVisible + "->" + visible);
         if (mVisible != visible) {
             mVisible = visible;
@@ -390,7 +379,7 @@
         }
     }
 
-    private void setMinimized(final boolean minimized) {
+    public void setMinimized(final boolean minimized) {
         if (DEBUG) Slog.d(TAG, "posting ext setMinimized " + minimized + " vis:" + mVisible);
         mMainExecutor.execute(() -> {
             if (DEBUG) Slog.d(TAG, "run posted ext setMinimized " + minimized + " vis:" + mVisible);
@@ -401,7 +390,7 @@
         });
     }
 
-    private void setHomeMinimized(final boolean minimized) {
+    public void setHomeMinimized(final boolean minimized) {
         if (DEBUG) {
             Slog.d(TAG, "setHomeMinimized  min:" + mMinimized + "->" + minimized + " hrsz:"
                     + mHomeStackResizable + " split:" + isDividerVisible());
@@ -441,7 +430,7 @@
         }
     }
 
-    void setAdjustedForIme(boolean adjustedForIme) {
+    public void setAdjustedForIme(boolean adjustedForIme) {
         if (mAdjustedForIme == adjustedForIme) {
             return;
         }
@@ -449,30 +438,30 @@
         updateTouchable();
     }
 
-    private void updateTouchable() {
+    public void updateTouchable() {
         mWindowManager.setTouchable(!mAdjustedForIme);
     }
 
-    private void onUndockingTask() {
+    public void onUndockingTask() {
         if (mView != null) {
             mView.onUndockingTask();
         }
     }
 
-    private void onAppTransitionFinished() {
+    public void onAppTransitionFinished() {
         if (mView == null) {
             return;
         }
         mForcedResizableController.onAppTransitionFinished();
     }
 
-    private void dump(PrintWriter pw) {
+    public void dump(PrintWriter pw) {
         pw.print("  mVisible="); pw.println(mVisible);
         pw.print("  mMinimized="); pw.println(mMinimized);
         pw.print("  mAdjustedForIme="); pw.println(mAdjustedForIme);
     }
 
-    long getAnimDuration() {
+    public long getAnimDuration() {
         float transitionScale = Settings.Global.getFloat(mContext.getContentResolver(),
                 Settings.Global.TRANSITION_ANIMATION_SCALE,
                 mContext.getResources().getFloat(
@@ -482,14 +471,14 @@
         return (long) (transitionDuration * transitionScale);
     }
 
-    void registerInSplitScreenListener(Consumer<Boolean> listener) {
+    public void registerInSplitScreenListener(Consumer<Boolean> listener) {
         listener.accept(isDividerVisible());
         synchronized (mDockedStackExistsListeners) {
             mDockedStackExistsListeners.add(new WeakReference<>(listener));
         }
     }
 
-    void unregisterInSplitScreenListener(Consumer<Boolean> listener) {
+    public void unregisterInSplitScreenListener(Consumer<Boolean> listener) {
         synchronized (mDockedStackExistsListeners) {
             for (int i = mDockedStackExistsListeners.size() - 1; i >= 0; i--) {
                 if (mDockedStackExistsListeners.get(i) == listener) {
@@ -499,13 +488,13 @@
         }
     }
 
-    private void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) {
+    public void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) {
         synchronized (mBoundsChangedListeners) {
             mBoundsChangedListeners.add(new WeakReference<>(listener));
         }
     }
 
-    private boolean splitPrimaryTask() {
+    public boolean splitPrimaryTask() {
         try {
             if (ActivityTaskManager.getService().getLockTaskModeState() == LOCK_TASK_MODE_PINNED
                     || isSplitActive()) {
@@ -538,12 +527,12 @@
                 topRunningTask.taskId, true /* onTop */);
     }
 
-    private void dismissSplitToPrimaryTask() {
+    public void dismissSplitToPrimaryTask() {
         startDismissSplit(true /* toPrimaryTask */);
     }
 
     /** Notifies the bounds of split screen changed. */
-    void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
+    public void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
         synchronized (mBoundsChangedListeners) {
             mBoundsChangedListeners.removeIf(wf -> {
                 BiConsumer<Rect, Rect> l = wf.get();
@@ -553,19 +542,19 @@
         }
     }
 
-    void startEnterSplit() {
+    public void startEnterSplit() {
         update(mDisplayController.getDisplayContext(
                 mContext.getDisplayId()).getResources().getConfiguration());
         // Set resizable directly here because applyEnterSplit already resizes home stack.
         mHomeStackResizable = mWindowManagerProxy.applyEnterSplit(mSplits, mSplitLayout);
     }
 
-    void prepareEnterSplitTransition(WindowContainerTransaction outWct) {
+    public void prepareEnterSplitTransition(WindowContainerTransaction outWct) {
         // Set resizable directly here because buildEnterSplit already resizes home stack.
         mHomeStackResizable = mWindowManagerProxy.buildEnterSplit(outWct, mSplits, mSplitLayout);
     }
 
-    void finishEnterSplitTransition(boolean minimized) {
+    public void finishEnterSplitTransition(boolean minimized) {
         update(mDisplayController.getDisplayContext(
                 mContext.getDisplayId()).getResources().getConfiguration());
         if (minimized) {
@@ -575,11 +564,11 @@
         }
     }
 
-    void startDismissSplit(boolean toPrimaryTask) {
+    public void startDismissSplit(boolean toPrimaryTask) {
         startDismissSplit(toPrimaryTask, false /* snapped */);
     }
 
-    void startDismissSplit(boolean toPrimaryTask, boolean snapped) {
+    public void startDismissSplit(boolean toPrimaryTask, boolean snapped) {
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             mSplits.getSplitTransitions().dismissSplit(
                     mSplits, mSplitLayout, !toPrimaryTask, snapped);
@@ -589,7 +578,7 @@
         }
     }
 
-    void onDismissSplit() {
+    public void onDismissSplit() {
         updateVisibility(false /* visible */);
         mMinimized = false;
         // Resets divider bar position to undefined, so new divider bar will apply default position
@@ -599,7 +588,7 @@
         mImePositionProcessor.reset();
     }
 
-    void ensureMinimizedSplit() {
+    public void ensureMinimizedSplit() {
         setHomeMinimized(true /* minimized */);
         if (mView != null && !isDividerVisible()) {
             // Wasn't in split-mode yet, so enter now.
@@ -610,7 +599,7 @@
         }
     }
 
-    void ensureNormalSplit() {
+    public void ensureNormalSplit() {
         setHomeMinimized(false /* minimized */);
         if (mView != null && !isDividerVisible()) {
             // Wasn't in split-mode, so enter now.
@@ -621,15 +610,15 @@
         }
     }
 
-    LegacySplitDisplayLayout getSplitLayout() {
+    public LegacySplitDisplayLayout getSplitLayout() {
         return mSplitLayout;
     }
 
-    WindowManagerProxy getWmProxy() {
+    public WindowManagerProxy getWmProxy() {
         return mWindowManagerProxy;
     }
 
-    WindowContainerToken getSecondaryRoot() {
+    public WindowContainerToken getSecondaryRoot() {
         if (mSplits == null || mSplits.mSecondary == null) {
             return null;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index e958648..11c11f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -69,9 +69,4 @@
      * 3 button navigation mode only
      */
     void registerGestureCallback(OneHandedGestureEventCallback callback);
-
-    /**
-     * Dump one handed status.
-     */
-    void dump(@NonNull PrintWriter pw);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index eaa704f..5a3c38b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -134,10 +134,10 @@
 
 
     /**
-     * Creates {@link OneHanded}, returns {@code null} if the feature is not supported.
+     * Creates {@link OneHandedController}, returns {@code null} if the feature is not supported.
      */
     @Nullable
-    public static OneHanded create(
+    public static OneHandedController create(
             Context context, DisplayController displayController,
             TaskStackListenerImpl taskStackListener, UiEventLogger uiEventLogger,
             ShellExecutor mainExecutor, Handler mainHandler) {
@@ -166,7 +166,7 @@
         return new OneHandedController(context, displayController,
                 oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler,
                 gestureHandler, timeoutHandler, oneHandedUiEventsLogger, overlayManager,
-                taskStackListener, mainExecutor, mainHandler).mImpl;
+                taskStackListener, mainExecutor, mainHandler);
     }
 
     @VisibleForTesting
@@ -228,6 +228,10 @@
                 mAccessibilityStateChangeListener);
     }
 
+    public OneHanded asOneHanded() {
+        return mImpl;
+    }
+
     /**
      * Set one handed enabled or disabled when user update settings
      */
@@ -468,7 +472,7 @@
         }
     }
 
-    private void dump(@NonNull PrintWriter pw) {
+    public void dump(@NonNull PrintWriter pw) {
         final String innerPrefix = "  ";
         pw.println(TAG + "states: ");
         pw.print(innerPrefix + "mOffSetFraction=");
@@ -561,12 +565,5 @@
                 OneHandedController.this.registerGestureCallback(callback);
             });
         }
-
-        @Override
-        public void dump(@NonNull PrintWriter pw) {
-            mMainExecutor.execute(() -> {
-                OneHandedController.this.dump(pw);
-            });
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index ad6f435..3064af6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -67,6 +67,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
 import com.android.wm.shell.transition.Transitions;
 
@@ -131,7 +132,7 @@
     private final PipUiEventLogger mPipUiEventLoggerLogger;
     private final int mEnterExitAnimationDuration;
     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
-    private final Optional<LegacySplitScreen> mSplitScreenOptional;
+    private final Optional<LegacySplitScreenController> mSplitScreenOptional;
     protected final ShellTaskOrganizer mTaskOrganizer;
     protected final ShellExecutor mMainExecutor;
 
@@ -207,7 +208,7 @@
             @NonNull PipAnimationController pipAnimationController,
             @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
             @NonNull PipTransitionController pipTransitionController,
-            Optional<LegacySplitScreen> splitScreenOptional,
+            Optional<LegacySplitScreenController> splitScreenOptional,
             @NonNull DisplayController displayController,
             @NonNull PipUiEventLogger pipUiEventLogger,
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -1047,7 +1048,8 @@
     }
 
     /**
-     * Sync with {@link LegacySplitScreen} on destination bounds if PiP is going to split screen.
+     * Sync with {@link LegacySplitScreenController} on destination bounds if PiP is going to split
+     * screen.
      *
      * @param destinationBoundsOut contain the updated destination bounds if applicable
      * @return {@code true} if destinationBounds is altered for split screen
@@ -1057,7 +1059,7 @@
             return false;
         }
 
-        LegacySplitScreen legacySplitScreen = mSplitScreenOptional.get();
+        LegacySplitScreenController legacySplitScreen = mSplitScreenOptional.get();
         if (!legacySplitScreen.isDividerVisible()) {
             // fail early if system is not in split screen mode
             return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java
deleted file mode 100644
index 11f22ed..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.sizecompatui;
-
-import android.annotation.Nullable;
-import android.graphics.Rect;
-import android.os.IBinder;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-/**
- * Interface to engage size compat mode UI.
- */
-@ExternalThread
-public interface SizeCompatUI {
-    /**
-     * Called when the Task info changed. Creates and updates the restart button if there is an
-     * activity in size compat, or removes the restart button if there is no size compat activity.
-     *
-     * @param displayId display the task and activity are in.
-     * @param taskId task the activity is in.
-     * @param taskBounds task bounds to place the restart button in.
-     * @param sizeCompatActivity the size compat activity in the task. Can be {@code null} if the
-     *                           top activity in this Task is not in size compat.
-     * @param taskListener listener to handle the Task Surface placement.
-     */
-    void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
-            @Nullable IBinder sizeCompatActivity,
-            @Nullable ShellTaskOrganizer.TaskListener taskListener);
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
index 286c3b6..48ee86c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
@@ -48,7 +48,6 @@
     private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
 
     @VisibleForTesting
-    final SizeCompatUI mImpl = new SizeCompatUIImpl();
     private final Context mContext;
     private final ShellExecutor mMainExecutor;
     private final DisplayController mDisplayController;
@@ -57,17 +56,8 @@
     /** Only show once automatically in the process life. */
     private boolean mHasShownHint;
 
-    /** Creates the {@link SizeCompatUIController}. */
-    public static SizeCompatUI create(Context context,
-            DisplayController displayController,
-            DisplayImeController imeController,
-            ShellExecutor mainExecutor) {
-        return new SizeCompatUIController(context, displayController, imeController, mainExecutor)
-                .mImpl;
-    }
-
     @VisibleForTesting
-    SizeCompatUIController(Context context,
+    public SizeCompatUIController(Context context,
             DisplayController displayController,
             DisplayImeController imeController,
             ShellExecutor mainExecutor) {
@@ -79,7 +69,7 @@
         mImeController.addPositionProcessor(this);
     }
 
-    private void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
+    public void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
             @Nullable IBinder sizeCompatActivity,
             @Nullable ShellTaskOrganizer.TaskListener taskListener) {
         // TODO Draw button on Task surface
@@ -177,15 +167,4 @@
         }
         return context;
     }
-
-    private class SizeCompatUIImpl implements SizeCompatUI {
-        @Override
-        public void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
-                @Nullable IBinder sizeCompatActivity,
-                @Nullable ShellTaskOrganizer.TaskListener taskListener) {
-            mMainExecutor.execute(() ->
-                    SizeCompatUIController.this.onSizeCompatInfoChanged(displayId, taskId,
-                            taskBounds, sizeCompatActivity, taskListener));
-        }
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
index 111362a..ecc066b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
@@ -24,7 +24,6 @@
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.BySelector
 import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
-import com.android.server.wm.flicker.helpers.closePipWindow
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow
 import com.android.wm.shell.flicker.pip.tv.isFocusedOrHasFocusedChild
@@ -113,7 +112,7 @@
         if (isTelevision) {
             uiDevice.closeTvPipWindow()
         } else {
-            uiDevice.closePipWindow()
+            closePipWindow(WindowManagerStateHelper(mInstrumentation))
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
index a14b46e..d56ed02 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
@@ -16,13 +16,11 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.os.Bundle
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.wm.shell.flicker.helpers.FixedAppHelper
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
@@ -50,8 +48,7 @@
         @JvmStatic
         fun getParams(): List<Array<Any>> {
             val testApp = FixedAppHelper(instrumentation)
-            val baseConfig = getTransitionLaunch(eachRun = true)
-            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+            val testSpec = getTransition(eachRun = true) { configuration ->
                 setup {
                     eachRun {
                         testApp.launchViaIntent(wmHelper)
@@ -97,7 +94,7 @@
                     }
                 }
             }
-            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
                 testSpec, supportedRotations = listOf(Surface.ROTATION_0),
                 repetitions = 5)
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 99a40da..ff31ba7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -16,13 +16,11 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.os.Bundle
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
@@ -50,9 +48,8 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
-            val baseConfig = getTransitionLaunch(
-                eachRun = true, stringExtras = emptyMap())
-            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+            val testSpec = getTransition(eachRun = true,
+                stringExtras = emptyMap()) { configuration ->
                 transitions {
                     pipApp.clickEnterPipButton()
                     pipApp.expandPipWindow(wmHelper)
@@ -92,7 +89,7 @@
                 }
             }
 
-            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
                 testSpec, supportedRotations = listOf(Surface.ROTATION_0),
                 repetitions = 5)
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index 7576e24..f054e64 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -16,13 +16,11 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.os.Bundle
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.startRotation
@@ -48,8 +46,7 @@
         @JvmStatic
         fun getParams(): Collection<Array<Any>> {
             val imeApp = ImeAppHelper(instrumentation)
-            val baseConfig = getTransitionLaunch(eachRun = false)
-            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+            val testSpec = getTransition(eachRun = false) { configuration ->
                 setup {
                     test {
                         imeApp.launchViaIntent(wmHelper)
@@ -90,7 +87,7 @@
             }
 
             return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
-                baseConfig, testSpec, supportedRotations = listOf(Surface.ROTATION_0),
+                testSpec, supportedRotations = listOf(Surface.ROTATION_0),
                 repetitions = 5)
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index adab5e8..ade65ac 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -16,13 +16,11 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.os.Bundle
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.endRotation
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.setRotation
@@ -55,8 +53,7 @@
         @JvmStatic
         fun getParams(): Collection<Array<Any>> {
             val fixedApp = FixedAppHelper(instrumentation)
-            val baseConfig = getTransitionLaunch(eachRun = false)
-            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+            val testSpec = getTransition(eachRun = false) { configuration ->
                 setup {
                     test {
                         fixedApp.launchViaIntent(wmHelper)
@@ -112,8 +109,7 @@
             }
 
             return FlickerTestRunnerFactory.getInstance().buildRotationTest(instrumentation,
-                baseConfig, testSpec,
-                supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
+                testSpec, supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
                 repetitions = 5)
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
index 4b826ff..f2d5899 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
@@ -16,13 +16,11 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.os.Bundle
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.focusChanges
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
@@ -52,8 +50,7 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
-            val baseConfig = getTransitionLaunch(eachRun = true)
-            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+            val testSpec = getTransition(eachRun = true) { configuration ->
                 setup {
                     eachRun {
                         this.setRotation(configuration.startRotation)
@@ -110,7 +107,7 @@
                 }
             }
 
-            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
                 testSpec, supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
index 62e8221..1b44377 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
@@ -16,13 +16,11 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.os.Bundle
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.focusChanges
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
@@ -52,8 +50,7 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
-            val baseConfig = getTransitionLaunch(eachRun = true)
-            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+            val testSpec = getTransition(eachRun = true) { configuration ->
                 setup {
                     eachRun {
                         this.setRotation(configuration.startRotation)
@@ -111,7 +108,7 @@
                 }
             }
 
-            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
                 testSpec, supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt
index eb7bae1..b1e404e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt
@@ -22,8 +22,6 @@
 import android.view.Surface
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.buildTestTag
-import com.android.server.wm.flicker.helpers.closePipWindow
-import com.android.server.wm.flicker.helpers.hasPipWindow
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.repetitions
@@ -83,10 +81,6 @@
                 }
                 test {
                     removeAllTasksButHome()
-
-                    if (device.hasPipWindow()) {
-                        device.closePipWindow()
-                    }
                     pipApp.exit()
                 }
             }
@@ -98,11 +92,13 @@
      *
      * @param eachRun If the pip app should be launched in each run (otherwise only 1x per test)
      * @param stringExtras Arguments to pass to the PIP launch intent
+     * @param extraSpec Addicional segment of flicker specification
      */
     @JvmOverloads
-    fun getTransitionLaunch(
+    open fun getTransition(
         eachRun: Boolean,
-        stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true")
+        stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true"),
+        extraSpec: FlickerBuilder.(Bundle) -> Unit = {}
     ): FlickerBuilder.(Bundle) -> Unit {
         return { configuration ->
             setupAndTeardown(this, configuration)
@@ -135,6 +131,8 @@
                     removeAllTasksButHome()
                 }
             }
+
+            extraSpec(this, configuration)
         }
     }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 80ea9b9..176b33d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -51,7 +51,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
+import com.android.wm.shell.sizecompatui.SizeCompatUIController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -76,7 +76,7 @@
     @Mock
     private Context mContext;
     @Mock
-    private SizeCompatUI mSizeCompatUI;
+    private SizeCompatUIController mSizeCompatUI;
 
     ShellTaskOrganizer mOrganizer;
     private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 9430af9..d10c036 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -44,6 +44,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.phone.PhonePipMenuController;
 
 import org.junit.Before;
@@ -70,7 +71,7 @@
     @Mock private PipTransitionController mMockPipTransitionController;
     @Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
     @Mock private PipUiEventLogger mMockPipUiEventLogger;
-    @Mock private Optional<LegacySplitScreen> mMockOptionalSplitScreen;
+    @Mock private Optional<LegacySplitScreenController> mMockOptionalSplitScreen;
     @Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
     private TestShellExecutor mMainExecutor;
     private PipBoundsState mPipBoundsState;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
index 98f01ff..0eb64e5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
@@ -86,7 +86,7 @@
         final Rect taskBounds = new Rect(0, 0, 1000, 2000);
 
         // Verify that the restart button is added with non-null size compat activity.
-        mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
+        mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
                 mMockActivityToken, mMockTaskListener);
         mShellMainExecutor.flushAll();
 
@@ -94,7 +94,7 @@
         verify(mMockButton).updateLastTargetActivity(eq(mMockActivityToken));
 
         // Verify that the restart button is removed with null size compat activity.
-        mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, null, null, null);
+        mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, null, null, null);
 
         mShellMainExecutor.flushAll();
         verify(mMockButton).remove();
@@ -104,7 +104,7 @@
     public void testChangeButtonVisibilityOnImeShowHide() {
         final int taskId = 12;
         final Rect taskBounds = new Rect(0, 0, 1000, 2000);
-        mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
+        mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
                 mMockActivityToken, mMockTaskListener);
         mShellMainExecutor.flushAll();
 
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index 8f455fe..1842356 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -355,29 +355,41 @@
     env->SetStaticObjectField(cls, fid, typeface);
 }
 
+// Critical Native
+static jint Typeface_getFamilySize(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) {
+    return toTypeface(faceHandle)->fFontCollection->getFamilies().size();
+}
+
+// Critical Native
+static jlong Typeface_getFamily(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle, jint index) {
+    std::shared_ptr<minikin::FontFamily> family =
+            toTypeface(faceHandle)->fFontCollection->getFamilies()[index];
+    return reinterpret_cast<jlong>(new FontFamilyWrapper(std::move(family)));
+}
 
 ///////////////////////////////////////////////////////////////////////////////
 
 static const JNINativeMethod gTypefaceMethods[] = {
-    { "nativeCreateFromTypeface", "(JI)J", (void*)Typeface_createFromTypeface },
-    { "nativeCreateFromTypefaceWithExactStyle", "(JIZ)J",
-            (void*)Typeface_createFromTypefaceWithExactStyle },
-    { "nativeCreateFromTypefaceWithVariation", "(JLjava/util/List;)J",
-            (void*)Typeface_createFromTypefaceWithVariation },
-    { "nativeCreateWeightAlias",  "(JI)J", (void*)Typeface_createWeightAlias },
-    { "nativeGetReleaseFunc",     "()J",  (void*)Typeface_getReleaseFunc },
-    { "nativeGetStyle",           "(J)I",  (void*)Typeface_getStyle },
-    { "nativeGetWeight",      "(J)I",  (void*)Typeface_getWeight },
-    { "nativeCreateFromArray",    "([JJII)J",
-                                           (void*)Typeface_createFromArray },
-    { "nativeSetDefault",         "(J)V",   (void*)Typeface_setDefault },
-    { "nativeGetSupportedAxes",   "(J)[I",  (void*)Typeface_getSupportedAxes },
-    { "nativeRegisterGenericFamily", "(Ljava/lang/String;J)V",
-          (void*)Typeface_registerGenericFamily },
-    { "nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;[J)I", (void*)Typeface_writeTypefaces},
-    { "nativeReadTypefaces", "(Ljava/nio/ByteBuffer;)[J", (void*)Typeface_readTypefaces},
-    { "nativeForceSetStaticFinalField", "(Ljava/lang/String;Landroid/graphics/Typeface;)V",
-          (void*)Typeface_forceSetStaticFinalField },
+        {"nativeCreateFromTypeface", "(JI)J", (void*)Typeface_createFromTypeface},
+        {"nativeCreateFromTypefaceWithExactStyle", "(JIZ)J",
+         (void*)Typeface_createFromTypefaceWithExactStyle},
+        {"nativeCreateFromTypefaceWithVariation", "(JLjava/util/List;)J",
+         (void*)Typeface_createFromTypefaceWithVariation},
+        {"nativeCreateWeightAlias", "(JI)J", (void*)Typeface_createWeightAlias},
+        {"nativeGetReleaseFunc", "()J", (void*)Typeface_getReleaseFunc},
+        {"nativeGetStyle", "(J)I", (void*)Typeface_getStyle},
+        {"nativeGetWeight", "(J)I", (void*)Typeface_getWeight},
+        {"nativeCreateFromArray", "([JJII)J", (void*)Typeface_createFromArray},
+        {"nativeSetDefault", "(J)V", (void*)Typeface_setDefault},
+        {"nativeGetSupportedAxes", "(J)[I", (void*)Typeface_getSupportedAxes},
+        {"nativeRegisterGenericFamily", "(Ljava/lang/String;J)V",
+         (void*)Typeface_registerGenericFamily},
+        {"nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;[J)I", (void*)Typeface_writeTypefaces},
+        {"nativeReadTypefaces", "(Ljava/nio/ByteBuffer;)[J", (void*)Typeface_readTypefaces},
+        {"nativeForceSetStaticFinalField", "(Ljava/lang/String;Landroid/graphics/Typeface;)V",
+         (void*)Typeface_forceSetStaticFinalField},
+        {"nativeGetFamilySize", "(J)I", (void*)Typeface_getFamilySize},
+        {"nativeGetFamily", "(JI)J", (void*)Typeface_getFamily},
 };
 
 int register_android_graphics_Typeface(JNIEnv* env)
diff --git a/libs/incident/Android.bp b/libs/incident/Android.bp
index d291ec0..438a92e 100644
--- a/libs/incident/Android.bp
+++ b/libs/incident/Android.bp
@@ -95,7 +95,7 @@
     name: "libincident_test",
     test_config: "AndroidTest.xml",
     defaults: ["libincidentpriv_defaults"],
-    test_suites: ["device-tests", "mts"],
+    test_suites: ["device-tests", "mts-statsd"],
     compile_multilib: "both",
     multilib: {
         lib64: {
diff --git a/media/java/android/media/DrmInitData.java b/media/java/android/media/DrmInitData.java
index 85b4ba5..3c48f8f 100644
--- a/media/java/android/media/DrmInitData.java
+++ b/media/java/android/media/DrmInitData.java
@@ -19,6 +19,7 @@
 import android.media.MediaDrm;
 
 import java.util.Arrays;
+import java.util.Objects;
 import java.util.UUID;
 
 /**
@@ -94,9 +95,9 @@
          * @param data The initialization data.
          */
         public SchemeInitData(@NonNull UUID uuid, @NonNull String mimeType, @NonNull byte[] data) {
-            this.uuid = uuid;
-            this.mimeType = mimeType;
-            this.data = data;
+            this.uuid = Objects.requireNonNull(uuid);
+            this.mimeType = Objects.requireNonNull(mimeType);
+            this.data = Objects.requireNonNull(data);
         }
 
         @Override
diff --git a/media/java/android/media/IRingtonePlayer.aidl b/media/java/android/media/IRingtonePlayer.aidl
index 02fa94c..5a7ff7f 100644
--- a/media/java/android/media/IRingtonePlayer.aidl
+++ b/media/java/android/media/IRingtonePlayer.aidl
@@ -33,7 +33,8 @@
         float volume, boolean looping, in @nullable VolumeShaper.Configuration volumeShaperConfig);
     oneway void stop(IBinder token);
     boolean isPlaying(IBinder token);
-    oneway void setPlaybackProperties(IBinder token, float volume, boolean looping);
+    oneway void setPlaybackProperties(IBinder token, float volume, boolean looping,
+        boolean hapticGeneratorEnabled);
 
     /** Used for Notification sound playback. */
     oneway void playAsync(in Uri uri, in UserHandle user, boolean looping, in AudioAttributes aa);
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index bd783ce..79d505e 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -24,6 +24,7 @@
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources.NotFoundException;
 import android.database.Cursor;
+import android.media.audiofx.HapticGenerator;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -77,6 +78,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private MediaPlayer mLocalPlayer;
     private final MyOnCompletionListener mCompletionListener = new MyOnCompletionListener();
+    private HapticGenerator mHapticGenerator;
 
     @UnsupportedAppUsage
     private Uri mUri;
@@ -89,6 +91,7 @@
     // playback properties, use synchronized with mPlaybackSettingsLock
     private boolean mIsLooping = false;
     private float mVolume = 1.0f;
+    private boolean mHapticGeneratorEnabled = false;
     private final Object mPlaybackSettingsLock = new Object();
 
     /** {@hide} */
@@ -197,15 +200,50 @@
     }
 
     /**
+     * Enable or disable the {@link android.media.audiofx.HapticGenerator} effect. The effect can
+     * only be enabled on devices that support the effect.
+     *
+     * @return true if the HapticGenerator effect is successfully enabled. Otherwise, return false.
+     * @see android.media.audiofx.HapticGenerator#isAvailable()
+     */
+    public boolean setHapticGeneratorEnabled(boolean enabled) {
+        if (!HapticGenerator.isAvailable()) {
+            return false;
+        }
+        synchronized (mPlaybackSettingsLock) {
+            mHapticGeneratorEnabled = enabled;
+            applyPlaybackProperties_sync();
+        }
+        return true;
+    }
+
+    /**
+     * Return whether the {@link android.media.audiofx.HapticGenerator} effect is enabled or not.
+     * @return true if the HapticGenerator is enabled.
+     */
+    public boolean isHapticGeneratorEnabled() {
+        synchronized (mPlaybackSettingsLock) {
+            return mHapticGeneratorEnabled;
+        }
+    }
+
+    /**
      * Must be called synchronized on mPlaybackSettingsLock
      */
     private void applyPlaybackProperties_sync() {
         if (mLocalPlayer != null) {
             mLocalPlayer.setVolume(mVolume);
             mLocalPlayer.setLooping(mIsLooping);
+            if (mHapticGenerator == null && mHapticGeneratorEnabled) {
+                mHapticGenerator = HapticGenerator.create(mLocalPlayer.getAudioSessionId());
+            }
+            if (mHapticGenerator != null) {
+                mHapticGenerator.setEnabled(mHapticGeneratorEnabled);
+            }
         } else if (mAllowRemote && (mRemotePlayer != null)) {
             try {
-                mRemotePlayer.setPlaybackProperties(mRemoteToken, mVolume, mIsLooping);
+                mRemotePlayer.setPlaybackProperties(
+                        mRemoteToken, mVolume, mIsLooping, mHapticGeneratorEnabled);
             } catch (RemoteException e) {
                 Log.w(TAG, "Problem setting playback properties: ", e);
             }
@@ -413,6 +451,10 @@
 
     private void destroyLocalPlayer() {
         if (mLocalPlayer != null) {
+            if (mHapticGenerator != null) {
+                mHapticGenerator.release();
+                mHapticGenerator = null;
+            }
             mLocalPlayer.setOnCompletionListener(null);
             mLocalPlayer.reset();
             mLocalPlayer.release();
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 98b9ad8..740bc2d 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -2526,6 +2526,7 @@
          * Pauses TV program recording in the current recording session.
          *
          * @param params A set of extra parameters which might be handled with this event.
+         *        {@link TvRecordingClient#pauseRecording(Bundle)}.
          */
         void pauseRecording(@NonNull Bundle params) {
             if (mToken == null) {
@@ -2543,6 +2544,7 @@
          * Resumes TV program recording in the current recording session.
          *
          * @param params A set of extra parameters which might be handled with this event.
+         *        {@link TvRecordingClient#resumeRecording(Bundle)}.
          */
         void resumeRecording(@NonNull Bundle params) {
             if (mToken == null) {
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 65b64d7..4972529 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -180,6 +180,10 @@
         "libstagefright_foundation_headers",
     ],
 
+    // TunerService is a system service required for Tuner feature.
+    // TunerJNI is a client of TunerService so we build the dependency here.
+    required: ["mediatuner"],
+
     export_include_dirs: ["."],
 
     cflags: [
diff --git a/media/jni/tuner/ClientHelper.h b/media/jni/tuner/ClientHelper.h
index 185b2f6..508dccf 100644
--- a/media/jni/tuner/ClientHelper.h
+++ b/media/jni/tuner/ClientHelper.h
@@ -19,6 +19,7 @@
 
 #include <android/binder_parcel_utils.h>
 #include <android/hardware/tv/tuner/1.1/types.h>
+#include <utils/Log.h>
 
 using Status = ::ndk::ScopedAStatus;
 
@@ -37,6 +38,7 @@
         } else if (s.isOk()) {
             return Result::SUCCESS;
         }
+        ALOGE("Aidl exception code %s", s.getDescription().c_str());
         return Result::UNKNOWN_ERROR;
     }
 };
diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp
index 8b4ca37..f618890 100644
--- a/media/jni/tuner/FilterClient.cpp
+++ b/media/jni/tuner/FilterClient.cpp
@@ -43,6 +43,7 @@
 using ::android::hardware::tv::tuner::V1_0::DemuxTpid;
 using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterSettings;
 using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterType;
+using ::android::hardware::tv::tuner::V1_1::DemuxFilterMonitorEvent;
 using ::android::hardware::tv::tuner::V1_1::ScramblingStatus;
 
 namespace android {
@@ -480,7 +481,7 @@
         case DemuxIpAddress::SrcIpAddress::hidl_discriminator::v4: {
             int size = ipAddr.srcIpAddress.v4().size();
             srcIpAddress.isIpV6 = false;
-            srcIpAddress.addr.resize(ipAddr.srcIpAddress.v4().size());
+            srcIpAddress.addr.resize(size);
             copy(&ipAddr.srcIpAddress.v4()[0], &ipAddr.srcIpAddress.v4()[size],
                     srcIpAddress.addr.begin());
             break;
@@ -493,8 +494,6 @@
                     srcIpAddress.addr.begin());
             break;
         }
-        default:
-            break;
     }
     switch (ipAddr.dstIpAddress.getDiscriminator()) {
         case DemuxIpAddress::DstIpAddress::hidl_discriminator::v4: {
@@ -513,8 +512,6 @@
                     dstIpAddress.addr.begin());
             break;
         }
-        default:
-            break;
     }
 }
 
@@ -696,8 +693,6 @@
             getHidlRestartEvent(filterEvents, eventExt);
             break;
         }
-        default:
-            break;
     }
 }
 
@@ -883,19 +878,18 @@
         DemuxFilterEventExt& eventExt) {
     auto monitor = filterEvents[0].get<TunerFilterEvent::monitor>();
     eventExt.events.resize(1);
+    DemuxFilterMonitorEvent monitorEvent;
     switch (monitor.getTag()) {
         case TunerFilterMonitorEvent::scramblingStatus: {
-            eventExt.events[0].monitorEvent().scramblingStatus(
-                    static_cast<ScramblingStatus>(monitor.scramblingStatus));
+            monitorEvent.scramblingStatus(static_cast<ScramblingStatus>(monitor.scramblingStatus));
+            eventExt.events[0].monitorEvent(monitorEvent);
             break;
         }
         case TunerFilterMonitorEvent::cid: {
-            eventExt.events[0].monitorEvent().cid(static_cast<uint32_t>(monitor.cid));
+            monitorEvent.cid(static_cast<uint32_t>(monitor.cid));
+            eventExt.events[0].monitorEvent(monitorEvent);
             break;
         }
-        default:
-            eventExt.events[0].noinit();
-            break;
     }
 }
 
diff --git a/media/jni/tuner/FrontendClient.cpp b/media/jni/tuner/FrontendClient.cpp
index 3a00133..0613223 100644
--- a/media/jni/tuner/FrontendClient.cpp
+++ b/media/jni/tuner/FrontendClient.cpp
@@ -49,9 +49,12 @@
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtGuardInterval;
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtMode;
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtModulation;
+using ::android::hardware::tv::tuner::V1_0::FrontendModulationStatus;
 using ::android::hardware::tv::tuner::V1_0::FrontendScanAtsc3PlpInfo;
+using ::android::hardware::tv::tuner::V1_0::FrontendStatusAtsc3PlpInfo;
 using ::android::hardware::tv::tuner::V1_0::LnbVoltage;
 using ::android::hardware::tv::tuner::V1_1::Constant;
+using ::android::hardware::tv::tuner::V1_1::FrontendBandwidth;
 using ::android::hardware::tv::tuner::V1_1::FrontendCableTimeInterleaveMode;
 using ::android::hardware::tv::tuner::V1_1::FrontendDtmbBandwidth;
 using ::android::hardware::tv::tuner::V1_1::FrontendDtmbGuardInterval;
@@ -61,19 +64,22 @@
 using ::android::hardware::tv::tuner::V1_1::FrontendDvbcBandwidth;
 using ::android::hardware::tv::tuner::V1_1::FrontendDvbtConstellation;
 using ::android::hardware::tv::tuner::V1_1::FrontendDvbtTransmissionMode;
+using ::android::hardware::tv::tuner::V1_1::FrontendGuardInterval;
+using ::android::hardware::tv::tuner::V1_1::FrontendInterleaveMode;
 using ::android::hardware::tv::tuner::V1_1::FrontendModulation;
+using ::android::hardware::tv::tuner::V1_1::FrontendRollOff;
 using ::android::hardware::tv::tuner::V1_1::FrontendSpectralInversion;
+using ::android::hardware::tv::tuner::V1_1::FrontendTransmissionMode;
 using ::android::hardware::tv::tuner::V1_1::FrontendType;
 
 namespace android {
 
 /////////////// FrontendClient ///////////////////////
 
-FrontendClient::FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int id, int type) {
+FrontendClient::FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int type) {
     mTunerFrontend = tunerFrontend;
     mAidlCallback = NULL;
     mHidlCallback = NULL;
-    mId = id;
     mType = type;
 }
 
@@ -104,6 +110,11 @@
     mFrontend_1_1 = ::android::hardware::tv::tuner::V1_1::IFrontend::castFrom(mFrontend);
 }
 
+// TODO: move after migration is done
+void FrontendClient::setId(int id) {
+    mId = id;
+}
+
 Result FrontendClient::tune(const FrontendSettings& settings,
         const FrontendSettingsExt1_1& settingsExt1_1) {
     if (mTunerFrontend != NULL) {
@@ -333,13 +344,26 @@
 }
 
 int FrontendClient::getId() {
-    return mId;
+    if (mTunerFrontend != NULL) {
+        Status s = mTunerFrontend->getFrontendId(&mId);
+        if (ClientHelper::getServiceSpecificErrorCode(s) == Result::SUCCESS) {
+            return mId;
+        }
+        ALOGE("Failed to getFrontendId from Tuner Frontend");
+        return -1;
+    }
+
+    if (mFrontend != NULL) {
+        return mId;
+    }
+
+    return -1;
 }
 
 vector<FrontendStatus> FrontendClient::getHidlStatus(vector<TunerFrontendStatus>& aidlStatus) {
     vector<FrontendStatus> hidlStatus;
     for (TunerFrontendStatus s : aidlStatus) {
-        FrontendStatus status;
+        FrontendStatus status = FrontendStatus();
         switch (s.getTag()) {
             case TunerFrontendStatus::isDemodLocked: {
                 status.isDemodLocked(s.get<TunerFrontendStatus::isDemodLocked>());
@@ -389,25 +413,31 @@
             }
             case TunerFrontendStatus::modulation: {
                 auto aidlMod = s.get<TunerFrontendStatus::modulation>();
+                FrontendModulationStatus modulation;
                 switch (mType) {
                     case (int)FrontendType::DVBC:
-                        status.modulation().dvbc(static_cast<FrontendDvbcModulation>(aidlMod));
+                        modulation.dvbc(static_cast<FrontendDvbcModulation>(aidlMod));
+                        status.modulation(modulation);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DVBS:
-                        status.modulation().dvbs(static_cast<FrontendDvbsModulation>(aidlMod));
+                        modulation.dvbs(static_cast<FrontendDvbsModulation>(aidlMod));
+                        status.modulation(modulation);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBS:
-                        status.modulation().isdbs(static_cast<FrontendIsdbsModulation>(aidlMod));
+                        modulation.isdbs(static_cast<FrontendIsdbsModulation>(aidlMod));
+                        status.modulation(modulation);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBS3:
-                        status.modulation().isdbs3(static_cast<FrontendIsdbs3Modulation>(aidlMod));
+                        modulation.isdbs3(static_cast<FrontendIsdbs3Modulation>(aidlMod));
+                        status.modulation(modulation);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBT:
-                        status.modulation().isdbt(static_cast<FrontendIsdbtModulation>(aidlMod));
+                        modulation.isdbt(static_cast<FrontendIsdbtModulation>(aidlMod));
+                        status.modulation(modulation);
                         hidlStatus.push_back(status);
                         break;
                     default:
@@ -466,7 +496,7 @@
             }
             case TunerFrontendStatus::hierarchy: {
                 status.hierarchy(static_cast<FrontendDvbtHierarchy>(
-                        s.get<TunerFrontendStatus::freqOffset>()));
+                        s.get<TunerFrontendStatus::hierarchy>()));
                 hidlStatus.push_back(status);
                 break;
             }
@@ -477,15 +507,16 @@
             }
             case TunerFrontendStatus::plpInfo: {
                 int size = s.get<TunerFrontendStatus::plpInfo>().size();
-                status.plpInfo().resize(size);
+                hidl_vec<FrontendStatusAtsc3PlpInfo> info(size);
                 for (int i = 0; i < size; i++) {
                     auto aidlInfo = s.get<TunerFrontendStatus::plpInfo>()[i];
-                    status.plpInfo()[i] = {
+                    info[i] = {
                         .plpId = (uint8_t)aidlInfo.plpId,
                         .isLocked = aidlInfo.isLocked,
                         .uec = (uint32_t)aidlInfo.uec,
                     };
                 }
+                status.plpInfo(info);
                 hidlStatus.push_back(status);
                 break;
             }
@@ -503,52 +534,54 @@
         FrontendStatusExt1_1 status;
         switch (s.getTag()) {
             case TunerFrontendStatus::modulations: {
+                vector<FrontendModulation> ms;
                 for (auto aidlMod : s.get<TunerFrontendStatus::modulations>()) {
-                    int size = status.modulations().size();
-                    status.modulations().resize(size + 1);
+                    FrontendModulation m;
                     switch (mType) {
                         case (int)FrontendType::DVBC:
-                            status.modulations()[size].dvbc(
-                                    static_cast<FrontendDvbcModulation>(aidlMod));
+                            m.dvbc(static_cast<FrontendDvbcModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::DVBS:
-                            status.modulations()[size].dvbs(
-                                    static_cast<FrontendDvbsModulation>(aidlMod));
+                            m.dvbs(static_cast<FrontendDvbsModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::DVBT:
-                            status.modulations()[size].dvbt(
-                                    static_cast<FrontendDvbtConstellation>(aidlMod));
+                            m.dvbt(static_cast<FrontendDvbtConstellation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::ISDBS:
-                            status.modulations()[size].isdbs(
-                                    static_cast<FrontendIsdbsModulation>(aidlMod));
+                            m.isdbs(static_cast<FrontendIsdbsModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::ISDBS3:
-                            status.modulations()[size].isdbs3(
-                                    static_cast<FrontendIsdbs3Modulation>(aidlMod));
+                            m.isdbs3(static_cast<FrontendIsdbs3Modulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::ISDBT:
-                            status.modulations()[size].isdbt(
-                                    static_cast<FrontendIsdbtModulation>(aidlMod));
+                            m.isdbt(static_cast<FrontendIsdbtModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::ATSC:
-                            status.modulations()[size].atsc(
-                                    static_cast<FrontendAtscModulation>(aidlMod));
+                            m.atsc(static_cast<FrontendAtscModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::ATSC3:
-                            status.modulations()[size].atsc3(
-                                    static_cast<FrontendAtsc3Modulation>(aidlMod));
+                            m.atsc3(static_cast<FrontendAtsc3Modulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::DTMB:
-                            status.modulations()[size].dtmb(
-                                    static_cast<FrontendDtmbModulation>(aidlMod));
+                            m.dtmb(static_cast<FrontendDtmbModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         default:
-                            status.modulations().resize(size);
                             break;
                     }
                 }
-                hidlStatus.push_back(status);
+                if (ms.size() > 0) {
+                    status.modulations(ms);
+                    hidlStatus.push_back(status);
+                }
                 break;
             }
             case TunerFrontendStatus::bers: {
@@ -571,25 +604,31 @@
             }
             case TunerFrontendStatus::bandwidth: {
                 auto aidlBand = s.get<TunerFrontendStatus::bandwidth>();
+                FrontendBandwidth band;
                 switch (mType) {
                     case (int)FrontendType::ATSC3:
-                        status.bandwidth().atsc3(static_cast<FrontendAtsc3Bandwidth>(aidlBand));
+                        band.atsc3(static_cast<FrontendAtsc3Bandwidth>(aidlBand));
+                        status.bandwidth(band);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DVBC:
-                        status.bandwidth().dvbc(static_cast<FrontendDvbcBandwidth>(aidlBand));
+                        band.dvbc(static_cast<FrontendDvbcBandwidth>(aidlBand));
+                        status.bandwidth(band);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DVBT:
-                        status.bandwidth().dvbt(static_cast<FrontendDvbtBandwidth>(aidlBand));
+                        band.dvbt(static_cast<FrontendDvbtBandwidth>(aidlBand));
+                        status.bandwidth(band);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBT:
-                        status.bandwidth().isdbt(static_cast<FrontendIsdbtBandwidth>(aidlBand));
+                        band.isdbt(static_cast<FrontendIsdbtBandwidth>(aidlBand));
+                        status.bandwidth(band);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DTMB:
-                        status.bandwidth().dtmb(static_cast<FrontendDtmbBandwidth>(aidlBand));
+                        band.dtmb(static_cast<FrontendDtmbBandwidth>(aidlBand));
+                        status.bandwidth(band);
                         hidlStatus.push_back(status);
                         break;
                     default:
@@ -599,17 +638,21 @@
             }
             case TunerFrontendStatus::interval: {
                 auto aidlInter = s.get<TunerFrontendStatus::interval>();
+                FrontendGuardInterval inter;
                 switch (mType) {
                     case (int)FrontendType::DVBT:
-                        status.interval().dvbt(static_cast<FrontendDvbtGuardInterval>(aidlInter));
+                        inter.dvbt(static_cast<FrontendDvbtGuardInterval>(aidlInter));
+                        status.interval(inter);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBT:
-                        status.interval().isdbt(static_cast<FrontendIsdbtGuardInterval>(aidlInter));
+                        inter.isdbt(static_cast<FrontendIsdbtGuardInterval>(aidlInter));
+                        status.interval(inter);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DTMB:
-                        status.interval().dtmb(static_cast<FrontendDtmbGuardInterval>(aidlInter));
+                        inter.dtmb(static_cast<FrontendDtmbGuardInterval>(aidlInter));
+                        status.interval(inter);
                         hidlStatus.push_back(status);
                         break;
                     default:
@@ -619,19 +662,21 @@
             }
             case TunerFrontendStatus::transmissionMode: {
                 auto aidlTran = s.get<TunerFrontendStatus::transmissionMode>();
+                FrontendTransmissionMode trans;
                 switch (mType) {
                     case (int)FrontendType::DVBT:
-                        status.transmissionMode().dvbt(
-                                static_cast<FrontendDvbtTransmissionMode>(aidlTran));
+                        trans.dvbt(static_cast<FrontendDvbtTransmissionMode>(aidlTran));
+                        status.transmissionMode(trans);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBT:
-                        status.transmissionMode().isdbt(static_cast<FrontendIsdbtMode>(aidlTran));
+                        trans.isdbt(static_cast<FrontendIsdbtMode>(aidlTran));
+                        status.transmissionMode(trans);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DTMB:
-                        status.transmissionMode().dtmb(
-                                static_cast<FrontendDtmbTransmissionMode>(aidlTran));
+                        trans.dtmb(static_cast<FrontendDtmbTransmissionMode>(aidlTran));
+                        status.transmissionMode(trans);
                         hidlStatus.push_back(status);
                         break;
                     default:
@@ -650,28 +695,30 @@
                 break;
             }
             case TunerFrontendStatus::interleaving: {
+                vector<FrontendInterleaveMode> modes;
                 for (auto aidlInter : s.get<TunerFrontendStatus::interleaving>()) {
-                    int size = status.interleaving().size();
-                    status.interleaving().resize(size + 1);
+                    FrontendInterleaveMode mode;
                     switch (mType) {
                         case (int)FrontendType::DVBC:
-                            status.interleaving()[size].dvbc(
-                                    static_cast<FrontendCableTimeInterleaveMode>(aidlInter));
+                            mode.dvbc(static_cast<FrontendCableTimeInterleaveMode>(aidlInter));
+                            modes.push_back(mode);
                             break;
                         case (int)FrontendType::ATSC3:
-                            status.interleaving()[size].atsc3(
-                                    static_cast<FrontendAtsc3TimeInterleaveMode>(aidlInter));
+                            mode.atsc3(static_cast<FrontendAtsc3TimeInterleaveMode>(aidlInter));
+                            modes.push_back(mode);
                             break;
                         case (int)FrontendType::DTMB:
-                            status.interleaving()[size].dtmb(
-                                    static_cast<FrontendDtmbTimeInterleaveMode>(aidlInter));
+                            mode.dtmb(static_cast<FrontendDtmbTimeInterleaveMode>(aidlInter));
+                            modes.push_back(mode);
                             break;
                         default:
-                            status.interleaving().resize(size);
                             break;
                     }
                 }
-                hidlStatus.push_back(status);
+                if (modes.size() > 0) {
+                    status.interleaving(modes);
+                    hidlStatus.push_back(status);
+                }
                 break;
             }
             case TunerFrontendStatus::isdbtSegment: {
@@ -690,17 +737,21 @@
             }
             case TunerFrontendStatus::rollOff: {
                 auto aidlRoll = s.get<TunerFrontendStatus::rollOff>();
+                FrontendRollOff roll;
                 switch (mType) {
                     case (int)FrontendType::DVBS:
-                        status.rollOff().dvbs(static_cast<FrontendDvbsRolloff>(aidlRoll));
+                        roll.dvbs(static_cast<FrontendDvbsRolloff>(aidlRoll));
+                        status.rollOff(roll);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBS:
-                        status.rollOff().isdbs(static_cast<FrontendIsdbsRolloff>(aidlRoll));
+                        roll.isdbs(static_cast<FrontendIsdbsRolloff>(aidlRoll));
+                        status.rollOff(roll);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBS3:
-                        status.rollOff().isdbs3(static_cast<FrontendIsdbs3Rolloff>(aidlRoll));
+                        roll.isdbs3(static_cast<FrontendIsdbs3Rolloff>(aidlRoll));
+                        status.rollOff(roll);
                         hidlStatus.push_back(status);
                         break;
                     default:
diff --git a/media/jni/tuner/FrontendClient.h b/media/jni/tuner/FrontendClient.h
index 298b397..f71616c 100644
--- a/media/jni/tuner/FrontendClient.h
+++ b/media/jni/tuner/FrontendClient.h
@@ -108,7 +108,7 @@
 struct FrontendClient : public RefBase {
 
 public:
-    FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int id, int type);
+    FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int type);
     ~FrontendClient();
 
     /**
@@ -180,6 +180,7 @@
 
     shared_ptr<ITunerFrontend> getAidlFrontend();
 
+    void setId(int id);
     int getId();
 
 private:
diff --git a/media/jni/tuner/TunerClient.cpp b/media/jni/tuner/TunerClient.cpp
index 7f954b5..cf17ed6 100644
--- a/media/jni/tuner/TunerClient.cpp
+++ b/media/jni/tuner/TunerClient.cpp
@@ -46,13 +46,12 @@
     // Connect with Tuner Service.
     ::ndk::SpAIBinder binder(AServiceManager_getService("media.tuner"));
     mTunerService = ITunerService::fromBinder(binder);
-    // TODO: Remove after JNI migration is done.
-    mTunerService = NULL;
     if (mTunerService == NULL) {
         ALOGE("Failed to get tuner service");
     } else {
         // TODO: b/178124017 update TRM in TunerService independently.
         mTunerService->updateTunerResources();
+        mTunerService->getTunerHalVersion(&mTunerVersion);
     }
 }
 
@@ -115,7 +114,7 @@
         if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) {
             return NULL;
         }
-        return new FrontendClient(tunerFrontend, frontendHandle, aidlFrontendInfo.type);
+        return new FrontendClient(tunerFrontend, aidlFrontendInfo.type);
     }
 
     if (mTuner != NULL) {
@@ -127,8 +126,10 @@
             if (res != Result::SUCCESS) {
                 return NULL;
             }
-            sp<FrontendClient> frontendClient = new FrontendClient(NULL, id, (int)hidlInfo.type);
+            sp<FrontendClient> frontendClient = new FrontendClient(
+                    NULL, (int)hidlInfo.type);
             frontendClient->setHidlFrontend(hidlFrontend);
+            frontendClient->setId(id);
             return frontendClient;
         }
     }
@@ -358,7 +359,7 @@
 
 sp<ITuner> TunerClient::getHidlTuner() {
     if (mTuner == NULL) {
-        mTunerVersion = 0;
+        mTunerVersion = TUNER_HAL_VERSION_UNKNOWN;
         mTuner_1_1 = ::android::hardware::tv::tuner::V1_1::ITuner::getService();
 
         if (mTuner_1_1 == NULL) {
@@ -367,11 +368,11 @@
             if (mTuner == NULL) {
                 ALOGW("Failed to get tuner 1.0 service.");
             } else {
-                mTunerVersion = 1 << 16;
+                mTunerVersion = TUNER_HAL_VERSION_1_0;
             }
         } else {
             mTuner = static_cast<sp<ITuner>>(mTuner_1_1);
-            mTunerVersion = ((1 << 16) | 1);
+            mTunerVersion = TUNER_HAL_VERSION_1_1;
          }
      }
      return mTuner;
diff --git a/media/jni/tuner/TunerClient.h b/media/jni/tuner/TunerClient.h
index 744bf20..9671cf7 100644
--- a/media/jni/tuner/TunerClient.h
+++ b/media/jni/tuner/TunerClient.h
@@ -48,6 +48,10 @@
 
 namespace android {
 
+const static int TUNER_HAL_VERSION_UNKNOWN = 0;
+const static int TUNER_HAL_VERSION_1_0 = 1 << 16;
+const static int TUNER_HAL_VERSION_1_1 = (1 << 16) | 1;
+
 typedef enum {
     FRONTEND,
     LNB,
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
index 09f285b..14a7cfa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
@@ -65,7 +65,7 @@
                 return toIconKey(TelephonyManager.NETWORK_TYPE_LTE) + "_CA_Plus";
             case TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA:
                 return toIconKey(TelephonyManager.NETWORK_TYPE_NR);
-            case TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE:
+            case TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED:
                 return toIconKey(TelephonyManager.NETWORK_TYPE_NR) + "_Plus";
             default:
                 return "unsupported";
@@ -180,7 +180,7 @@
                 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA),
                 TelephonyIcons.NR_5G);
         networkToIconLookup.put(toDisplayIconKey(
-                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE),
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED),
                 TelephonyIcons.NR_5G_PLUS);
         networkToIconLookup.put(toIconKey(
                 TelephonyManager.NETWORK_TYPE_NR),
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 1432544..1566b76 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -400,6 +400,7 @@
     <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
     <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
+    <uses-permission android:name="android.permission.BIND_RESUME_ON_REBOOT_SERVICE" />
 
     <application android:label="@string/app_label"
                 android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java
index 0831e0e..da079cf0 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java
@@ -103,5 +103,10 @@
         default Animator getOutAnimation() {
             return null;
         }
+
+        /**
+         * Called on orientation changes.
+         */
+        default void onOrientationChange(int orientation) {  }
     }
 }
diff --git a/packages/SystemUI/res/color/kg_user_avatar_frame.xml b/packages/SystemUI/res/color/kg_user_avatar_frame.xml
new file mode 100644
index 0000000..174981e
--- /dev/null
+++ b/packages/SystemUI/res/color/kg_user_avatar_frame.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 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
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_activated="true"
+        android:color="@color/kg_user_switcher_avatar_background" />
+    <item android:color="@color/kg_user_switcher_avatar_background" />
+</selector>
diff --git a/packages/SystemUI/res/drawable/end_guest_button_background.xml b/packages/SystemUI/res/drawable/end_guest_button_background.xml
new file mode 100644
index 0000000..5644b65
--- /dev/null
+++ b/packages/SystemUI/res/drawable/end_guest_button_background.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ 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
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <stroke
+        android:width="@dimen/end_guest_button_border_size"
+        android:color="?android:attr/colorControlHighlight" />
+    <corners android:radius="@dimen/end_guest_button_corner_radius" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/kg_bg_avatar.xml b/packages/SystemUI/res/drawable/kg_bg_avatar.xml
new file mode 100644
index 0000000..addb3f7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/kg_bg_avatar.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="48dp"
+        android:viewportWidth="100"
+        android:viewportHeight="100">
+
+    <path
+        android:fillColor="@color/kg_user_switcher_avatar_background"
+        android:pathData="M50,50m-50,0a50,50 0,1 1,100 0a50,50 0,1 1,-100 0"/>
+
+</vector>
diff --git a/packages/SystemUI/res/drawable/toast_background.xml b/packages/SystemUI/res/drawable/toast_background.xml
new file mode 100644
index 0000000..5c45e83
--- /dev/null
+++ b/packages/SystemUI/res/drawable/toast_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="#FFFFFFFF" />
+    <corners android:radius="@dimen/toast_bg_radius" />
+</shape>
diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index 416ee81..2789ed1 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -43,17 +43,12 @@
             <include layout="@layout/system_icons" />
         </FrameLayout>
 
-        <com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch"
-            android:layout_width="@dimen/multi_user_switch_width_keyguard"
-            android:layout_height="match_parent"
-            android:background="@drawable/ripple_drawable"
-            android:layout_marginEnd="@dimen/multi_user_switch_keyguard_margin">
-            <ImageView android:id="@+id/multi_user_avatar"
-                android:layout_width="@dimen/multi_user_avatar_keyguard_size"
-                android:layout_height="@dimen/multi_user_avatar_keyguard_size"
-                android:layout_gravity="center"
-                android:scaleType="centerInside"/>
-        </com.android.systemui.statusbar.phone.MultiUserSwitch>
+
+        <ImageView android:id="@+id/multi_user_avatar"
+            android:layout_width="@dimen/multi_user_avatar_keyguard_size"
+            android:layout_height="@dimen/multi_user_avatar_keyguard_size"
+            android:layout_gravity="center"
+            android:scaleType="centerInside"/>
     </LinearLayout>
 
     <Space
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher.xml b/packages/SystemUI/res/layout/keyguard_user_switcher.xml
index 983ba6d..253c03e 100644
--- a/packages/SystemUI/res/layout/keyguard_user_switcher.xml
+++ b/packages/SystemUI/res/layout/keyguard_user_switcher.xml
@@ -14,10 +14,50 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<view xmlns:android="http://schemas.android.com/apk/res/android"
-        class="com.android.systemui.statusbar.policy.KeyguardUserSwitcher$Container"
-        android:visibility="gone"
-        android:layout_height="match_parent"
-        android:layout_width="match_parent">
-    <!-- KeyguardUserSwitcher loads keyguard_user_switcher_inner.xml here -->
-</view>
\ No newline at end of file
+<!-- This is a view that shows a user switcher in Keyguard. -->
+<com.android.systemui.statusbar.policy.KeyguardUserSwitcherView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/keyguard_user_switcher_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="end">
+
+    <com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView
+        android:id="@+id/keyguard_user_switcher_list"
+        android:orientation="vertical"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_gravity="top|end"
+        android:gravity="end" />
+
+    <LinearLayout
+        android:id="@+id/end_guest_button"
+        android:layout_height="@dimen/end_guest_button_layout_height"
+        android:layout_width="wrap_content"
+        android:layout_gravity="center_horizontal|bottom"
+        android:layout_centerHorizontal="true"
+        android:layout_marginBottom="@dimen/end_guest_button_margin_bottom"
+        android:orientation="horizontal"
+        android:gravity="center"
+        android:paddingLeft="@dimen/end_guest_button_padding_horizontal"
+        android:paddingRight="@dimen/end_guest_button_padding_horizontal"
+        android:background="@drawable/end_guest_button_background"
+        android:visibility="gone">
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:src="@drawable/ic_exit_to_app"
+            android:background="@android:color/transparent"
+            android:color="?attr/wallpaperTextColor" />
+        <TextView
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:gravity="center"
+            android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
+            android:textColor="?attr/wallpaperTextColor"
+            android:textSize="13sp"
+            android:text="@string/guest_exit_button" />
+    </LinearLayout>
+
+</com.android.systemui.statusbar.policy.KeyguardUserSwitcherView>
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_inner.xml b/packages/SystemUI/res/layout/keyguard_user_switcher_inner.xml
deleted file mode 100644
index 4c1042e..0000000
--- a/packages/SystemUI/res/layout/keyguard_user_switcher_inner.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2016 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
-  -->
-<com.android.keyguard.AlphaOptimizedLinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/keyguard_user_switcher_inner"
-    android:orientation="vertical"
-    android:layout_height="wrap_content"
-    android:layout_width="wrap_content"
-    android:layout_marginTop="@dimen/status_bar_header_height_keyguard"
-    android:layout_gravity="end"
-    android:gravity="end"
-    android:paddingTop="4dp">
-</com.android.keyguard.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
index 1cd1a04..aaa372a 100644
--- a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
+++ b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
@@ -19,29 +19,30 @@
 <!-- LinearLayout -->
 <com.android.systemui.statusbar.policy.KeyguardUserDetailItemView
         xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:sysui="http://schemas.android.com/apk/res-auto"
+        xmlns:systemui="http://schemas.android.com/apk/res-auto"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:padding="8dp"
         android:layout_marginEnd="8dp"
-        android:gravity="center_vertical"
+        android:gravity="end|center_vertical"
         android:clickable="true"
-        android:background="@drawable/ripple_drawable"
-        sysui:regularTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher"
-        sysui:activatedTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher.Activated">
-    <TextView android:id="@+id/user_name"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginEnd="13dp"
-            android:textAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher"
-            />
-    <com.android.systemui.statusbar.phone.UserAvatarView android:id="@+id/user_picture"
-            android:layout_width="@dimen/kg_framed_avatar_size"
-            android:layout_height="@dimen/kg_framed_avatar_size"
-            android:contentDescription="@null"
-            sysui:frameWidth="@dimen/keyguard_user_switcher_border_thickness"
-            sysui:framePadding="2.5dp"
-            sysui:badgeDiameter="18dp"
-            sysui:badgeMargin="1dp"
-            sysui:frameColor="@color/kg_user_switcher_rounded_background_color" />
+        android:background="@drawable/kg_user_switcher_rounded_bg"
+        systemui:activatedTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher"
+        systemui:regularTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher">
+    <TextView
+        android:id="@+id/user_name"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="20dp"
+        android:layout_marginEnd="16dp" />
+    <com.android.systemui.statusbar.phone.UserAvatarView
+        android:id="@+id/user_picture"
+        android:layout_width="@dimen/kg_framed_avatar_size"
+        android:layout_height="@dimen/kg_framed_avatar_size"
+        systemui:avatarPadding="0dp"
+        systemui:badgeDiameter="18dp"
+        systemui:badgeMargin="1dp"
+        systemui:frameWidth="0dp"
+        systemui:framePadding="0dp"
+        systemui:frameColor="@color/kg_user_avatar_frame" />
 </com.android.systemui.statusbar.policy.KeyguardUserDetailItemView>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index d6385ff..859d904 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -31,6 +31,12 @@
         android:layout_height="match_parent"
         android:visibility="gone" />
 
+    <ViewStub
+        android:id="@+id/keyguard_user_switcher_stub"
+        android:layout="@layout/keyguard_user_switcher"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent" />
+
     <include
         layout="@layout/keyguard_status_view"
         android:visibility="gone" />
@@ -72,12 +78,6 @@
 
         <include layout="@layout/photo_preview_overlay" />
 
-        <ViewStub
-            android:id="@+id/keyguard_user_switcher"
-            android:layout="@layout/keyguard_user_switcher"
-            android:layout_height="match_parent"
-            android:layout_width="match_parent" />
-
         <include
             layout="@layout/keyguard_status_bar"
             android:visibility="invisible" />
diff --git a/packages/SystemUI/res/layout/text_toast.xml b/packages/SystemUI/res/layout/text_toast.xml
new file mode 100644
index 0000000..de4e062
--- /dev/null
+++ b/packages/SystemUI/res/layout/text_toast.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ 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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:maxWidth="@dimen/toast_width"
+    android:orientation="horizontal"
+    android:background="@drawable/toast_background"
+    android:backgroundTint="?android:attr/colorBackground"
+    android:layout_marginEnd="16dp"
+    android:layout_marginStart="16dp"
+    android:gravity="center_vertical">
+
+    <!-- Icon should be 24x24, make slightly larger to allow for shadowing, adjust via padding -->
+    <ImageView
+        android:id="@+id/icon"
+        android:alpha="@dimen/toast_icon_alpha"
+        android:padding="11.5dp"
+        android:layout_width="@dimen/toast_icon_size"
+        android:layout_height="@dimen/toast_icon_size"/>
+    <TextView
+        android:id="@+id/text"
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:paddingTop="12dp"
+        android:paddingBottom="12dp"
+        android:paddingStart="0dp"
+        android:paddingEnd="22dp"
+        android:textSize="@dimen/toast_text_size"
+        android:textColor="?android:attr/textColorPrimary"
+        android:fontFamily="@*android:string/config_headlineFontFamily"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"/>
+</LinearLayout>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 51d7b8e..24c7655 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -52,4 +52,6 @@
     <!-- (footer_height -48dp)/2 -->
     <dimen name="controls_management_footer_top_margin">4dp</dimen>
     <dimen name="controls_management_favorites_top_margin">8dp</dimen>
+
+    <dimen name="toast_y_offset">24dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 3153d0d..37ec576 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -89,6 +89,8 @@
     <color name="kg_user_switcher_avatar_icon_color">@android:color/background_light</color>
     <!-- Icon color for selected user avatars in keyguard user switcher -->
     <color name="kg_user_switcher_selected_avatar_icon_color">#202124</color>
+    <!-- Color of background circle of user avatars in keyguard user switcher -->
+    <color name="kg_user_switcher_avatar_background">#3C4043</color>
     <!-- Icon color for user avatars in quick settings user switcher  -->
     <color name="qs_user_switcher_avatar_icon_color">@android:color/background_light</color>
     <!-- Icon color for selected user avatars in quick settings user switcher  -->
diff --git a/packages/SystemUI/res/values-sw600dp/styles.xml b/packages/SystemUI/res/values-sw600dp/styles.xml
index 02bd602..ee2b82d 100644
--- a/packages/SystemUI/res/values-sw600dp/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp/styles.xml
@@ -23,13 +23,6 @@
         <item name="numColumns">4</item>
     </style>
 
-    <style name="TextAppearance.StatusBar.Expanded.UserSwitcher">
-        <item name="android:textSize">@dimen/kg_user_switcher_text_size</item>
-        <item name="android:textStyle">normal</item>
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
-        <item name="android:textColor">?attr/wallpaperTextColor</item>
-    </style>
-
     <style name="TextAppearance.QS.UserSwitcher">
         <item name="android:textSize">@dimen/kg_user_switcher_text_size</item>
         <item name="android:textColor">?android:attr/textColorSecondary</item>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 5fb6de7..8bd9de9 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -66,9 +66,13 @@
     <!-- Color for rounded background for activated user in keyguard user switcher -->
     <color name="kg_user_switcher_activated_background_color">#26000000</color>
     <!-- Icon color for user avatars in keyguard user switcher -->
-    <color name="kg_user_switcher_avatar_icon_color">@android:color/background_light</color>
-    <!-- Icon color for selected user avatars in keyguard user switcher -->
-    <color name="kg_user_switcher_selected_avatar_icon_color">@android:color/background_light</color>
+    <color name="kg_user_switcher_avatar_icon_color">@color/GM2_grey_800</color>
+    <!-- Icon color for user avatars in keyguard user switcher that restricted
+         (e.g. cannot be switched to) -->
+    <color name="kg_user_switcher_restricted_avatar_icon_color">@color/GM2_grey_600</color>
+    <!-- Color of background circle of user avatars in keyguard user switcher -->
+    <color name="kg_user_switcher_avatar_background">@color/GM2_grey_300</color>
+
     <!-- Icon color for user avatars in user switcher quick settings -->
     <color name="qs_user_switcher_avatar_icon_color">#3C4043</color>
     <!-- Icon color for selected user avatars in user switcher quick settings -->
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index bb04c3b..6196225 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -107,7 +107,7 @@
 
     <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
     <string name="quick_settings_tiles_stock" translatable="false">
-        wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle,mictoggle
+        wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle,mictoggle,controls
     </string>
 
     <!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 79471e6..594fbdf 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -743,9 +743,6 @@
     <!-- end margin for system icons if multi user switch is hidden -->
     <dimen name="system_icons_switcher_hidden_expanded_margin">16dp</dimen>
 
-    <!-- The thickness of the colored border around the current user. -->
-    <dimen name="keyguard_user_switcher_border_thickness">2dp</dimen>
-
     <dimen name="data_usage_graph_marker_width">4dp</dimen>
 
     <!-- The padding bottom of the clock group when QS is expanded. -->
@@ -805,7 +802,7 @@
     <!-- Size of user icon + frame in the qs user picker (incl. frame) -->
     <dimen name="qs_framed_avatar_size">54dp</dimen>
     <!-- Size of user icon + frame in the keyguard user picker (incl. frame) -->
-    <dimen name="kg_framed_avatar_size">54dp</dimen>
+    <dimen name="kg_framed_avatar_size">32dp</dimen>
 
     <!-- Margin on the left side of the carrier text on Keyguard -->
     <dimen name="keyguard_carrier_text_margin">16dp</dimen>
@@ -1324,8 +1321,16 @@
     <dimen name="screenrecord_status_icon_height">17.5dp</dimen>
     <dimen name="screenrecord_status_icon_bg_radius">8dp</dimen>
 
+    <!-- Keyguard user switcher -->
     <dimen name="kg_user_switcher_text_size">16sp</dimen>
 
+    <!-- End guest session button -->
+    <dimen name="end_guest_button_layout_height">32dp</dimen>
+    <dimen name="end_guest_button_padding_horizontal">16dp</dimen>
+    <dimen name="end_guest_button_margin_bottom">96dp</dimen>
+    <dimen name="end_guest_button_border_size">1dp</dimen>
+    <dimen name="end_guest_button_corner_radius">16dp</dimen>
+
     <!-- Opacity at which the background for the shutdown UI will be drawn. -->
     <item name="shutdown_scrim_behind_alpha" format="float" type="dimen">0.95</item>
 
@@ -1354,4 +1359,11 @@
     <dimen name="rounded_slider_icon_size">24dp</dimen>
     <!-- rounded_slider_icon_size / 2 -->
     <dimen name="rounded_slider_icon_inset">12dp</dimen>
+
+    <dimen name="toast_width">296dp</dimen>
+    <item name="toast_icon_alpha" format="float" type="dimen">1</item>
+    <dimen name="toast_text_size">14sp</dimen>
+    <dimen name="toast_y_offset">48dp</dimen>
+    <dimen name="toast_icon_size">48dp</dimen>
+    <dimen name="toast_bg_radius">28dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index ded8a2e..f83965e 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -23,6 +23,7 @@
     <bool name="flag_notif_updates">false</bool>
 
     <bool name="flag_shade_is_opaque">false</bool>
+    <bool name="flag_monet">false</bool>
 
     <!-- b/171917882 -->
     <bool name="flag_notification_twocolumn">false</bool>
@@ -39,4 +40,6 @@
 
     <!-- People Tile flag -->
     <bool name="flag_conversations">false</bool>
+
+    <bool name="flag_toast_style">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index abcf4e8..7c1c24b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1112,6 +1112,9 @@
     <!-- Name for a freshly added user [CHAR LIMIT=30] -->
     <string name="user_new_user_name">New user</string>
 
+    <!-- Label for button that exits guest session and clears the guest user data [CHAR LIMIT=50]-->
+    <string name="guest_exit_button">End guest session</string>
+
     <!-- Title of the confirmation dialog when exiting guest session [CHAR LIMIT=NONE] -->
     <string name="guest_exit_guest_dialog_title">End guest session?</string>
 
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 7c72548..85c470f 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -114,12 +114,12 @@
     <style name="TextAppearance.StatusBar.Expanded.UserSwitcher">
         <item name="android:textSize">@dimen/kg_user_switcher_text_size</item>
         <item name="android:textStyle">normal</item>
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:textColor">?attr/wallpaperTextColor</item>
     </style>
 
     <style name="TextAppearance.StatusBar.Expanded.UserSwitcher.Activated">
         <item name="android:fontWeight">700</item>
-        <item name="android:textStyle">bold</item>
     </style>
 
     <style name="TextAppearance" />
@@ -764,6 +764,7 @@
     <style name="TextAppearance.PrivacyDialog">
         <item name="android:textSize">14sp</item>
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
 
     <style name="UdfpsProgressBarStyle"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
index 7f04f28..72e4061 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
@@ -32,8 +32,29 @@
     private final Matrix mTmpTransform = new Matrix();
     private final float[] mTmpFloat9 = new float[9];
     private final RectF mTmpSourceRectF = new RectF();
+    private final RectF mTmpDestinationRectF = new RectF();
     private final Rect mTmpDestinationRect = new Rect();
 
+    public void scale(SurfaceControl.Transaction tx, SurfaceControl leash,
+            Rect sourceBounds, Rect destinationBounds) {
+        mTmpSourceRectF.set(sourceBounds);
+        mTmpDestinationRectF.set(destinationBounds);
+        mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
+        tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
+                .setPosition(leash, mTmpDestinationRectF.left, mTmpDestinationRectF.top);
+    }
+
+    public void scale(SurfaceControl.Transaction tx, SurfaceControl leash,
+            Rect sourceBounds, Rect destinationBounds,
+            float degree, float positionX, float positionY) {
+        mTmpSourceRectF.set(sourceBounds);
+        mTmpDestinationRectF.set(destinationBounds);
+        mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
+        mTmpTransform.postRotate(degree, 0, 0);
+        tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
+                .setPosition(leash, positionX, positionY);
+    }
+
     public void scaleAndCrop(SurfaceControl.Transaction tx, SurfaceControl leash,
             Rect sourceBounds, Rect destinationBounds, Rect insets) {
         mTmpSourceRectF.set(sourceBounds);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index a5f364d..6fb6760 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -16,13 +16,9 @@
 
 package com.android.keyguard;
 
-import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-
 import android.util.Slog;
 import android.view.View;
 
-import com.android.systemui.Interpolators;
-import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -50,13 +46,12 @@
 
     private final KeyguardSliceViewController mKeyguardSliceViewController;
     private final KeyguardClockSwitchController mKeyguardClockSwitchController;
-    private final KeyguardStateController mKeyguardStateController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final ConfigurationController mConfigurationController;
     private final NotificationIconAreaController mNotificationIconAreaController;
     private final DozeParameters mDozeParameters;
+    private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
 
-    private boolean mKeyguardStatusViewVisibilityAnimating;
     private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
 
     @Inject
@@ -72,11 +67,12 @@
         super(keyguardStatusView);
         mKeyguardSliceViewController = keyguardSliceViewController;
         mKeyguardClockSwitchController = keyguardClockSwitchController;
-        mKeyguardStateController = keyguardStateController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mConfigurationController = configurationController;
         mNotificationIconAreaController = notificationIconAreaController;
         mDozeParameters = dozeParameters;
+        mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
+                dozeParameters);
     }
 
     @Override
@@ -144,7 +140,7 @@
      * Set keyguard status view alpha.
      */
     public void setAlpha(float alpha) {
-        if (!mKeyguardStatusViewVisibilityAnimating) {
+        if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
             mView.setAlpha(alpha);
         }
     }
@@ -200,7 +196,7 @@
     public void updatePosition(int x, int y, float scale, boolean animate) {
         // We animate the status view visible/invisible using Y translation, so don't change it
         // while the animation is running.
-        if (!mKeyguardStatusViewVisibilityAnimating) {
+        if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
             PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, CLOCK_ANIMATION_PROPERTIES,
                     animate);
         }
@@ -230,69 +226,8 @@
             boolean keyguardFadingAway,
             boolean goingToFullShade,
             int oldStatusBarState) {
-        mView.animate().cancel();
-        mKeyguardStatusViewVisibilityAnimating = false;
-        if ((!keyguardFadingAway && oldStatusBarState == KEYGUARD
-                && statusBarState != KEYGUARD) || goingToFullShade) {
-            mKeyguardStatusViewVisibilityAnimating = true;
-            mView.animate()
-                    .alpha(0f)
-                    .setStartDelay(0)
-                    .setDuration(160)
-                    .setInterpolator(Interpolators.ALPHA_OUT)
-                    .withEndAction(
-                    mAnimateKeyguardStatusViewGoneEndRunnable);
-            if (keyguardFadingAway) {
-                mView.animate()
-                        .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
-                        .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
-                        .start();
-            }
-        } else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) {
-            mView.setVisibility(View.VISIBLE);
-            mKeyguardStatusViewVisibilityAnimating = true;
-            mView.setAlpha(0f);
-            mView.animate()
-                    .alpha(1f)
-                    .setStartDelay(0)
-                    .setDuration(320)
-                    .setInterpolator(Interpolators.ALPHA_IN)
-                    .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
-        } else if (statusBarState == KEYGUARD) {
-            if (keyguardFadingAway) {
-                mKeyguardStatusViewVisibilityAnimating = true;
-                mView.animate()
-                        .alpha(0)
-                        .translationYBy(-getHeight() * 0.05f)
-                        .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
-                        .setDuration(125)
-                        .setStartDelay(0)
-                        .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable)
-                        .start();
-            } else if (mDozeParameters.shouldControlUnlockedScreenOff()) {
-                mKeyguardStatusViewVisibilityAnimating = true;
-
-                mView.setVisibility(View.VISIBLE);
-                mView.setAlpha(0f);
-
-                float curTranslationY = mView.getTranslationY();
-                mView.setTranslationY(curTranslationY - getHeight() * 0.1f);
-                mView.animate()
-                        .setStartDelay((int) (StackStateAnimator.ANIMATION_DURATION_WAKEUP * .6f))
-                        .setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP)
-                        .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                        .alpha(1f)
-                        .translationY(curTranslationY)
-                        .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable)
-                        .start();
-            } else {
-                mView.setVisibility(View.VISIBLE);
-                mView.setAlpha(1f);
-            }
-        } else {
-            mView.setVisibility(View.GONE);
-            mView.setAlpha(1f);
-        }
+        mKeyguardVisibilityHelper.setViewVisibility(
+                statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState);
     }
 
     private void refreshTime() {
@@ -393,19 +328,4 @@
             mView.updateLogoutView();
         }
     };
-
-    private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = () -> {
-        mKeyguardStatusViewVisibilityAnimating = false;
-        mView.setVisibility(View.INVISIBLE);
-    };
-
-
-    private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = () -> {
-        mKeyguardStatusViewVisibilityAnimating = false;
-        mView.setVisibility(View.GONE);
-    };
-
-    private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> {
-        mKeyguardStatusViewVisibilityAnimating = false;
-    };
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
new file mode 100644
index 0000000..724e1f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -0,0 +1,137 @@
+/*
+ * 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.keyguard;
+
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+
+import android.view.View;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+/**
+ * Helper class for updating visibility of keyguard views based on keyguard and status bar state.
+ * This logic is shared by both the keyguard status view and the keyguard user switcher.
+ */
+public class KeyguardVisibilityHelper {
+
+    private View mView;
+    private final KeyguardStateController mKeyguardStateController;
+    private final DozeParameters mDozeParameters;
+    private boolean mKeyguardViewVisibilityAnimating;
+
+    public KeyguardVisibilityHelper(View view, KeyguardStateController keyguardStateController,
+            DozeParameters dozeParameters) {
+        mView = view;
+        mKeyguardStateController = keyguardStateController;
+        mDozeParameters = dozeParameters;
+    }
+
+    public boolean isVisibilityAnimating() {
+        return mKeyguardViewVisibilityAnimating;
+    }
+
+    /**
+     * Set the visibility of a keyguard view based on some new state.
+     */
+    public void setViewVisibility(
+            int statusBarState,
+            boolean keyguardFadingAway,
+            boolean goingToFullShade,
+            int oldStatusBarState) {
+        mView.animate().cancel();
+        mKeyguardViewVisibilityAnimating = false;
+        if ((!keyguardFadingAway && oldStatusBarState == KEYGUARD
+                && statusBarState != KEYGUARD) || goingToFullShade) {
+            mKeyguardViewVisibilityAnimating = true;
+            mView.animate()
+                    .alpha(0f)
+                    .setStartDelay(0)
+                    .setDuration(160)
+                    .setInterpolator(Interpolators.ALPHA_OUT)
+                    .withEndAction(
+                            mAnimateKeyguardStatusViewGoneEndRunnable);
+            if (keyguardFadingAway) {
+                mView.animate()
+                        .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
+                        .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
+                        .start();
+            }
+        } else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) {
+            mView.setVisibility(View.VISIBLE);
+            mKeyguardViewVisibilityAnimating = true;
+            mView.setAlpha(0f);
+            mView.animate()
+                    .alpha(1f)
+                    .setStartDelay(0)
+                    .setDuration(320)
+                    .setInterpolator(Interpolators.ALPHA_IN)
+                    .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
+        } else if (statusBarState == KEYGUARD) {
+            if (keyguardFadingAway) {
+                mKeyguardViewVisibilityAnimating = true;
+                mView.animate()
+                        .alpha(0)
+                        .translationYBy(-mView.getHeight() * 0.05f)
+                        .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
+                        .setDuration(125)
+                        .setStartDelay(0)
+                        .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable)
+                        .start();
+            } else if (mDozeParameters.shouldControlUnlockedScreenOff()) {
+                mKeyguardViewVisibilityAnimating = true;
+
+                mView.setVisibility(View.VISIBLE);
+                mView.setAlpha(0f);
+
+                float curTranslationY = mView.getTranslationY();
+                mView.setTranslationY(curTranslationY - mView.getHeight() * 0.1f);
+                mView.animate()
+                        .setStartDelay((int) (StackStateAnimator.ANIMATION_DURATION_WAKEUP * .6f))
+                        .setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP)
+                        .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+                        .alpha(1f)
+                        .translationY(curTranslationY)
+                        .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable)
+                        .start();
+            } else {
+                mView.setVisibility(View.VISIBLE);
+                mView.setAlpha(1f);
+            }
+        } else {
+            mView.setVisibility(View.GONE);
+            mView.setAlpha(1f);
+        }
+    }
+
+    private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = () -> {
+        mKeyguardViewVisibilityAnimating = false;
+        mView.setVisibility(View.INVISIBLE);
+    };
+
+    private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = () -> {
+        mKeyguardViewVisibilityAnimating = false;
+        mView.setVisibility(View.GONE);
+    };
+
+    private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> {
+        mKeyguardViewVisibilityAnimating = false;
+    };
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherComponent.java
new file mode 100644
index 0000000..730c14d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherComponent.java
@@ -0,0 +1,40 @@
+/*
+ * 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.keyguard.dagger;
+
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Subcomponent for helping work with KeyguardUserSwitcher and its children.
+ */
+@Subcomponent(modules = {KeyguardUserSwitcherModule.class})
+@KeyguardUserSwitcherScope
+public interface KeyguardUserSwitcherComponent {
+    /** Simple factory for {@link KeyguardUserSwitcherComponent}. */
+    @Subcomponent.Factory
+    interface Factory {
+        KeyguardUserSwitcherComponent build(
+                @BindsInstance KeyguardUserSwitcherView keyguardUserSwitcherView);
+    }
+
+    /** Builds a {@link com.android.systemui.statusbar.policy.KeyguardUserSwitcherController}. */
+    KeyguardUserSwitcherController getKeyguardUserSwitcherController();
+}
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherModule.java
similarity index 76%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherModule.java
index 14d57bf..b9184f4 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherModule.java
@@ -14,6 +14,11 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package com.android.keyguard.dagger;
 
-parcelable ExternalTimeSuggestion;
+import dagger.Module;
+
+/** Dagger module for {@link KeyguardUserSwitcherComponent}. */
+@Module
+public abstract class KeyguardUserSwitcherModule {
+}
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherScope.java
similarity index 61%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherScope.java
index 14d57bf..864472e 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherScope.java
@@ -14,6 +14,19 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package com.android.keyguard.dagger;
 
-parcelable ExternalTimeSuggestion;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
+
+/**
+ * Scope annotation for singleton items within the KeyguardUserSwitcherComponent.
+ */
+@Documented
+@Retention(RUNTIME)
+@Scope
+public @interface KeyguardUserSwitcherScope {}
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index caaee5f..1765627 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -49,7 +49,6 @@
 
 import androidx.annotation.StyleRes;
 
-import com.android.settingslib.Utils;
 import com.android.settingslib.graph.ThemedBatteryDrawable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher;
@@ -105,11 +104,6 @@
     private DualToneHandler mDualToneHandler;
     private int mUser;
 
-    /**
-     * Whether we should use colors that adapt based on wallpaper/the scrim behind quick settings.
-     */
-    private boolean mUseWallpaperTextColors;
-
     private int mNonAdaptedSingleToneColor;
     private int mNonAdaptedForegroundColor;
     private int mNonAdaptedBackgroundColor;
@@ -242,31 +236,6 @@
         mIsSubscribedForTunerUpdates = false;
     }
 
-    /**
-     * Sets whether the battery meter view uses the wallpaperTextColor. If we're not using it, we'll
-     * revert back to dark-mode-based/tinted colors.
-     *
-     * @param shouldUseWallpaperTextColor whether we should use wallpaperTextColor for all
-     *                                    components
-     */
-    public void useWallpaperTextColor(boolean shouldUseWallpaperTextColor) {
-        if (shouldUseWallpaperTextColor == mUseWallpaperTextColors) {
-            return;
-        }
-
-        mUseWallpaperTextColors = shouldUseWallpaperTextColor;
-
-        if (mUseWallpaperTextColors) {
-            updateColors(
-                    Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor),
-                    Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColorSecondary),
-                    Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor));
-        } else {
-            updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor,
-                    mNonAdaptedSingleToneColor);
-        }
-    }
-
     public void setColorsFromContext(Context context) {
         if (context == null) {
             return;
@@ -476,13 +445,19 @@
         mNonAdaptedForegroundColor = mDualToneHandler.getFillColor(intensity);
         mNonAdaptedBackgroundColor = mDualToneHandler.getBackgroundColor(intensity);
 
-        if (!mUseWallpaperTextColors) {
-            updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor,
-                    mNonAdaptedSingleToneColor);
-        }
+        updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor,
+                mNonAdaptedSingleToneColor);
     }
 
-    private void updateColors(int foregroundColor, int backgroundColor, int singleToneColor) {
+    /**
+     * Sets icon and text colors. This will be overridden by {@code onDarkChanged} events,
+     * if registered.
+     *
+     * @param foregroundColor
+     * @param backgroundColor
+     * @param singleToneColor
+     */
+    public void updateColors(int foregroundColor, int backgroundColor, int singleToneColor) {
         mDrawable.setColors(foregroundColor, backgroundColor, singleToneColor);
         mTextColor = singleToneColor;
         if (mBatteryPercentView != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 247f25e..6b300f4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -30,6 +30,7 @@
 import android.service.controls.actions.FloatAction
 import android.util.Log
 import android.view.HapticFeedbackConstants
+import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
@@ -71,7 +72,7 @@
     }
 
     override fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) {
-        bouncerOrRun(Action(cvh.cws.ci.controlId, {
+        bouncerOrRun(createAction(cvh.cws.ci.controlId, {
             cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
             cvh.action(BooleanAction(templateId, !isChecked))
         }, true /* blockable */))
@@ -79,7 +80,7 @@
 
     override fun touch(cvh: ControlViewHolder, templateId: String, control: Control) {
         val blockable = cvh.usePanel()
-        bouncerOrRun(Action(cvh.cws.ci.controlId, {
+        bouncerOrRun(createAction(cvh.cws.ci.controlId, {
             cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
             if (cvh.usePanel()) {
                 showDialog(cvh, control.getAppIntent().getIntent())
@@ -98,13 +99,13 @@
     }
 
     override fun setValue(cvh: ControlViewHolder, templateId: String, newValue: Float) {
-        bouncerOrRun(Action(cvh.cws.ci.controlId, {
+        bouncerOrRun(createAction(cvh.cws.ci.controlId, {
             cvh.action(FloatAction(templateId, newValue))
         }, false /* blockable */))
     }
 
     override fun longPress(cvh: ControlViewHolder) {
-        bouncerOrRun(Action(cvh.cws.ci.controlId, {
+        bouncerOrRun(createAction(cvh.cws.ci.controlId, {
             // Long press snould only be called when there is valid control state, otherwise ignore
             cvh.cws.control?.let {
                 cvh.layout.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
@@ -114,6 +115,7 @@
     }
 
     override fun runPendingAction(controlId: String) {
+        if (!keyguardStateController.isUnlocked()) return
         if (pendingAction?.controlId == controlId) {
             pendingAction?.invoke()
             pendingAction = null
@@ -135,7 +137,8 @@
             false
         }
 
-    private fun bouncerOrRun(action: Action) {
+    @VisibleForTesting
+    fun bouncerOrRun(action: Action) {
         if (keyguardStateController.isShowing()) {
             var closeDialog = !keyguardStateController.isUnlocked()
             if (closeDialog) {
@@ -190,6 +193,10 @@
         }
     }
 
+    @VisibleForTesting
+    fun createAction(controlId: String, f: () -> Unit, blockable: Boolean) =
+        Action(controlId, f, blockable)
+
     inner class Action(val controlId: String, val f: () -> Unit, val blockable: Boolean) {
         fun invoke() {
             if (!blockable || shouldRunAction(controlId)) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
index f533cfb..db68d17 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
@@ -28,11 +28,12 @@
 import com.android.systemui.Interpolators
 import com.android.systemui.R
 import com.android.systemui.broadcast.BroadcastDispatcher
+import javax.inject.Inject
 
 /**
  * Show the controls space inside a dialog, as from the lock screen.
  */
-class ControlsDialog(
+class ControlsDialog @Inject constructor(
     thisContext: Context,
     val broadcastDispatcher: BroadcastDispatcher
 ) : Dialog(thisContext, R.style.Theme_SystemUI_Dialog_Control_LockScreen) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 84dd259..f3726a3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -16,6 +16,11 @@
 
 package com.android.systemui.dagger;
 
+import android.content.Context;
+
+import com.android.systemui.SystemUIFactory;
+import com.android.systemui.tv.TvWMComponent;
+import com.android.systemui.wmshell.TvWMShellModule;
 import com.android.systemui.wmshell.WMShellModule;
 import com.android.wm.shell.ShellCommandHandler;
 import com.android.wm.shell.ShellInit;
@@ -34,7 +39,13 @@
 import dagger.Subcomponent;
 
 /**
- * Dagger Subcomponent for WindowManager.
+ * Dagger Subcomponent for WindowManager.  This class explicitly describes the interfaces exported
+ * from the WM component into the SysUI component (in
+ * {@link SystemUIFactory#init(Context, boolean)}), and references the specific dependencies
+ * provided by its particular device/form-factor SystemUI implementation.
+ *
+ * ie. {@link WMComponent} includes {@link WMShellModule}
+ *     and {@link TvWMComponent} includes {@link TvWMShellModule}
  */
 @WMSingleton
 @Subcomponent(modules = {WMShellModule.class})
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index c55fdf4..91cf710 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -415,6 +415,7 @@
 
         @Override
         public void onUserSwitching(int userId) {
+            if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId));
             // Note that the mLockPatternUtils user has already been updated from setCurrentUser.
             // We need to force a reset of the views, since lockNow (called by
             // ActivityManagerService) will not reconstruct the keyguard if it is already showing.
@@ -432,6 +433,7 @@
 
         @Override
         public void onUserSwitchComplete(int userId) {
+            if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
             if (userId != UserHandle.USER_SYSTEM) {
                 UserInfo info = UserManager.get(mContext).getUserInfo(userId);
                 // Don't try to dismiss if the user has Pin/Patter/Password set
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 76281d8..9e5b225 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -30,6 +30,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardViewController;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
+import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.classifier.FalsingModule;
@@ -61,7 +62,7 @@
 /**
  * Dagger Module providing {@link StatusBar}.
  */
-@Module(subcomponents = {KeyguardStatusViewComponent.class},
+@Module(subcomponents = {KeyguardStatusViewComponent.class, KeyguardUserSwitcherComponent.class},
         includes = {FalsingModule.class})
 public class KeyguardModule {
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index 4c96de2..553b6d8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -163,7 +163,8 @@
         }
 
         @Override
-        public void setPlaybackProperties(IBinder token, float volume, boolean looping) {
+        public void setPlaybackProperties(IBinder token, float volume, boolean looping,
+                boolean hapticGeneratorEnabled) {
             Client client;
             synchronized (mClients) {
                 client = mClients.get(token);
@@ -171,6 +172,7 @@
             if (client != null) {
                 client.mRingtone.setVolume(volume);
                 client.mRingtone.setLooping(looping);
+                client.mRingtone.setHapticGeneratorEnabled(hapticGeneratorEnabled);
             }
             // else no client for token when setting playback properties but will be set at play()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index f56a890..7820921 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -40,6 +40,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.settings.brightness.BrightnessController;
 import com.android.systemui.settings.brightness.BrightnessSlider;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.tuner.TunerService;
 
@@ -92,9 +93,10 @@
             DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
             QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
             BrightnessSlider.Factory brightnessSliderFactory,
-            @Named(QS_LABELS_FLAG) boolean qsLabelsFlag) {
+            @Named(QS_LABELS_FLAG) boolean qsLabelsFlag,
+            FeatureFlags featureFlags) {
         super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
-                metricsLogger, uiEventLogger, qsLogger, dumpManager);
+                metricsLogger, uiEventLogger, qsLogger, dumpManager, featureFlags);
         mQsSecurityFooter = qsSecurityFooter;
         mTunerService = tunerService;
         mQsCustomizerController = qsCustomizerController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index b02799f..9426e71 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -34,6 +34,8 @@
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.util.Utils;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.animation.DisappearParameters;
 
@@ -63,6 +65,7 @@
     private final UiEventLogger mUiEventLogger;
     private final QSLogger mQSLogger;
     private final DumpManager mDumpManager;
+    private final FeatureFlags mFeatureFlags;
     protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
 
     private int mLastOrientation;
@@ -93,11 +96,18 @@
 
     private boolean mUsingHorizontalLayout;
 
-    protected QSPanelControllerBase(T view, QSTileHost host,
+    protected QSPanelControllerBase(
+            T view,
+            QSTileHost host,
             QSCustomizerController qsCustomizerController,
-            @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer, MediaHost mediaHost,
-            MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
-            DumpManager dumpManager) {
+            @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
+            MediaHost mediaHost,
+            MetricsLogger metricsLogger,
+            UiEventLogger uiEventLogger,
+            QSLogger qsLogger,
+            DumpManager dumpManager,
+            FeatureFlags featureFlags
+    ) {
         super(view);
         mHost = host;
         mQsCustomizerController = qsCustomizerController;
@@ -107,6 +117,7 @@
         mUiEventLogger = uiEventLogger;
         mQSLogger = qsLogger;
         mDumpManager = dumpManager;
+        mFeatureFlags = featureFlags;
     }
 
     @Override
@@ -334,9 +345,12 @@
     }
 
     boolean shouldUseHorizontalLayout() {
+        if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, getResources()))  {
+            return false;
+        }
         return mUsingMediaPlayer && mMediaHost.getVisible()
-                && getResources().getConfiguration().orientation
-                == Configuration.ORIENTATION_LANDSCAPE;
+                    && getResources().getConfiguration().orientation
+                    == Configuration.ORIENTATION_LANDSCAPE;
     }
 
     private void logTiles() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index a0db200..383e932 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -30,6 +30,7 @@
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.statusbar.FeatureFlags;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -57,9 +58,11 @@
             @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
             @Named(QUICK_QS_PANEL) MediaHost mediaHost,
             MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
-            DumpManager dumpManager, @Named(QS_LABELS_FLAG) boolean qsLabelsFlag) {
+            DumpManager dumpManager, @Named(QS_LABELS_FLAG) boolean qsLabelsFlag,
+            FeatureFlags featureFlags
+    ) {
         super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
-                uiEventLogger, qsLogger, dumpManager);
+                uiEventLogger, qsLogger, dumpManager, featureFlags);
         mUseSideLabels = qsLabelsFlag;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 7776c12..87252ff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -29,7 +29,6 @@
 import android.util.AttributeSet;
 import android.util.MathUtils;
 import android.util.Pair;
-import android.view.ContextThemeWrapper;
 import android.view.DisplayCutout;
 import android.view.View;
 import android.view.ViewGroup;
@@ -48,7 +47,6 @@
 
 import com.android.settingslib.Utils;
 import com.android.systemui.BatteryMeterView;
-import com.android.systemui.DualToneHandler;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.privacy.OngoingPrivacyChip;
@@ -75,7 +73,6 @@
     protected QuickQSPanel mHeaderQsPanel;
     private TouchAnimator mStatusIconsAlphaAnimator;
     private TouchAnimator mHeaderTextContainerAlphaAnimator;
-    private DualToneHandler mDualToneHandler;
 
     private View mSystemIconsView;
     private View mQuickQsStatusIcons;
@@ -110,8 +107,6 @@
 
     public QuickStatusBarHeader(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mDualToneHandler = new DualToneHandler(
-                new ContextThemeWrapper(context, R.style.QSHeaderTheme));
     }
 
     @Override
@@ -149,10 +144,8 @@
     }
 
     void onAttach(TintedIconManager iconManager) {
-        int colorForeground = Utils.getColorAttrDefaultColor(getContext(),
-                android.R.attr.colorForeground);
-        float intensity = getColorIntensity(colorForeground);
-        int fillColor = mDualToneHandler.getSingleColor(intensity);
+        int fillColor = Utils.getColorAttrDefaultColor(getContext(),
+                android.R.attr.textColorPrimary);
 
         // Set the correct tint for the status icons so they contrast
         iconManager.setTint(fillColor);
@@ -271,14 +264,12 @@
 
         int textColor = Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary);
         if (textColor != mTextColorPrimary) {
+            int textColorSecondary = Utils.getColorAttrDefaultColor(mContext,
+                    android.R.attr.textColorSecondary);
             mTextColorPrimary = textColor;
             mClockView.setTextColor(textColor);
-
-            float intensity = getColorIntensity(textColor);
-            int fillColor = mDualToneHandler.getSingleColor(intensity);
-
-            Rect tintArea = new Rect(0, 0, 0, 0);
-            mBatteryRemainingIcon.onDarkChanged(tintArea, intensity, fillColor);
+            mBatteryRemainingIcon.updateColors(mTextColorPrimary, textColorSecondary,
+                    mTextColorPrimary);
         }
 
         updateStatusIconAlphaAnimator();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
index 7d8d86f..eddcf8c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
@@ -28,9 +28,7 @@
 
 import com.android.settingslib.Utils;
 import com.android.settingslib.graph.SignalDrawable;
-import com.android.systemui.DualToneHandler;
 import com.android.systemui.R;
-import com.android.systemui.qs.QuickStatusBarHeader;
 
 import java.util.Objects;
 
@@ -40,9 +38,6 @@
     private TextView mCarrierText;
     private ImageView mMobileSignal;
     private ImageView mMobileRoaming;
-    private DualToneHandler mDualToneHandler;
-    private ColorStateList mColorForegroundStateList;
-    private float mColorForegroundIntensity;
     private CellSignalState mLastSignalState;
 
     public QSCarrier(Context context) {
@@ -64,7 +59,6 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mDualToneHandler = new DualToneHandler(getContext());
         mMobileGroup = findViewById(R.id.mobile_combo);
         if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
             mMobileRoaming = findViewById(R.id.mobile_roaming_large);
@@ -74,11 +68,6 @@
         mMobileSignal = findViewById(R.id.mobile_signal);
         mCarrierText = findViewById(R.id.qs_carrier_text);
         mMobileSignal.setImageDrawable(new SignalDrawable(mContext));
-
-        int colorForeground = Utils.getColorAttrDefaultColor(mContext,
-                android.R.attr.colorForeground);
-        mColorForegroundStateList = ColorStateList.valueOf(colorForeground);
-        mColorForegroundIntensity = QuickStatusBarHeader.getColorIntensity(colorForeground);
     }
 
     /**
@@ -92,8 +81,8 @@
         mMobileGroup.setVisibility(state.visible ? View.VISIBLE : View.GONE);
         if (state.visible) {
             mMobileRoaming.setVisibility(state.roaming ? View.VISIBLE : View.GONE);
-            ColorStateList colorStateList = ColorStateList.valueOf(
-                    mDualToneHandler.getSingleColor(mColorForegroundIntensity));
+            ColorStateList colorStateList = Utils.getColorAttr(mContext,
+                    android.R.attr.textColorPrimary);
             mMobileRoaming.setImageTintList(colorStateList);
             mMobileSignal.setImageTintList(colorStateList);
             mMobileSignal.setImageLevel(state.mobileSignalIconId);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 11e6330..6983b38 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -37,6 +37,7 @@
 import com.android.systemui.qs.tiles.CellularTile;
 import com.android.systemui.qs.tiles.ColorInversionTile;
 import com.android.systemui.qs.tiles.DataSaverTile;
+import com.android.systemui.qs.tiles.DeviceControlsTile;
 import com.android.systemui.qs.tiles.DndTile;
 import com.android.systemui.qs.tiles.FlashlightTile;
 import com.android.systemui.qs.tiles.HotspotTile;
@@ -89,6 +90,7 @@
     private final Provider<ReduceBrightColorsTile> mReduceBrightColorsTileProvider;
     private final Provider<CameraToggleTile> mCameraToggleTileProvider;
     private final Provider<MicrophoneToggleTile> mMicrophoneToggleTileProvider;
+    private final Provider<DeviceControlsTile> mDeviceControlsTileProvider;
 
     private final Lazy<QSHost> mQsHostLazy;
     private final Provider<CustomTile.Builder> mCustomTileBuilderProvider;
@@ -123,7 +125,8 @@
             Provider<ScreenRecordTile> screenRecordTileProvider,
             Provider<ReduceBrightColorsTile> reduceBrightColorsTileProvider,
             Provider<CameraToggleTile> cameraToggleTileProvider,
-            Provider<MicrophoneToggleTile> microphoneToggleTileProvider) {
+            Provider<MicrophoneToggleTile> microphoneToggleTileProvider,
+            Provider<DeviceControlsTile> deviceControlsTileProvider) {
         mQsHostLazy = qsHostLazy;
         mCustomTileBuilderProvider = customTileBuilderProvider;
 
@@ -153,6 +156,7 @@
         mReduceBrightColorsTileProvider = reduceBrightColorsTileProvider;
         mCameraToggleTileProvider = cameraToggleTileProvider;
         mMicrophoneToggleTileProvider = microphoneToggleTileProvider;
+        mDeviceControlsTileProvider = deviceControlsTileProvider;
     }
 
     public QSTile createTile(String tileSpec) {
@@ -212,6 +216,8 @@
                 return mCameraToggleTileProvider.get();
             case "mictoggle":
                 return mMicrophoneToggleTileProvider.get();
+            case "controls":
+                return mDeviceControlsTileProvider.get();
         }
 
         // Custom tiles
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
new file mode 100644
index 0000000..6176a57
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles
+
+import android.content.Intent
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings
+import android.service.quicksettings.Tile
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.R
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.ControlsDialog
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.statusbar.FeatureFlags
+import com.android.systemui.util.settings.GlobalSettings
+import java.util.concurrent.atomic.AtomicBoolean
+import javax.inject.Inject
+import javax.inject.Provider
+
+class DeviceControlsTile @Inject constructor(
+    host: QSHost,
+    @Background backgroundLooper: Looper,
+    @Main mainHandler: Handler,
+    metricsLogger: MetricsLogger,
+    statusBarStateController: StatusBarStateController,
+    activityStarter: ActivityStarter,
+    qsLogger: QSLogger,
+    private val controlsComponent: ControlsComponent,
+    private val featureFlags: FeatureFlags,
+    private val dialogProvider: Provider<ControlsDialog>,
+    globalSettings: GlobalSettings
+) : QSTileImpl<QSTile.State>(
+        host,
+        backgroundLooper,
+        mainHandler,
+        metricsLogger,
+        statusBarStateController,
+        activityStarter,
+        qsLogger
+) {
+
+    companion object {
+        const val SETTINGS_FLAG = "controls_lockscreen"
+    }
+
+    private val controlsLockscreen = globalSettings.getInt(SETTINGS_FLAG, 0) != 0
+    private var hasControlsApps = AtomicBoolean(false)
+    private val intent = Intent(Settings.ACTION_DEVICE_CONTROLS_SETTINGS)
+
+    private var controlsDialog: ControlsDialog? = null
+    private val icon = ResourceIcon.get(R.drawable.ic_device_light)
+
+    private val listingCallback = object : ControlsListingController.ControlsListingCallback {
+        override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
+            if (hasControlsApps.compareAndSet(serviceInfos.isEmpty(), serviceInfos.isNotEmpty())) {
+                refreshState()
+            }
+        }
+    }
+
+    init {
+        controlsComponent.getControlsListingController().ifPresent {
+            it.observe(this, listingCallback)
+        }
+    }
+
+    override fun isAvailable(): Boolean {
+        return featureFlags.isKeyguardLayoutEnabled &&
+                controlsLockscreen &&
+                controlsComponent.getControlsUiController().isPresent
+    }
+
+    override fun newTileState(): QSTile.State {
+        return QSTile.State().also {
+            it.state = Tile.STATE_UNAVAILABLE // Start unavailable matching `hasControlsApps`
+        }
+    }
+
+    override fun handleDestroy() {
+        dismissDialog()
+        super.handleDestroy()
+    }
+
+    private fun createDialog() {
+        if (controlsDialog?.isShowing != true) {
+            controlsDialog = dialogProvider.get()
+        }
+    }
+
+    private fun dismissDialog() {
+        controlsDialog?.dismiss()?.also {
+            controlsDialog = null
+        }
+    }
+
+    override fun handleClick() {
+        if (state.state != Tile.STATE_UNAVAILABLE) {
+            mUiHandler.post {
+                createDialog()
+                controlsDialog?.show(controlsComponent.getControlsUiController().get())
+            }
+        }
+    }
+
+    override fun handleUpdateState(state: QSTile.State, arg: Any?) {
+        state.label = tileLabel
+        state.secondaryLabel = ""
+        state.stateDescription = ""
+        state.contentDescription = state.label
+        state.icon = icon
+        if (hasControlsApps.get()) {
+            state.state = Tile.STATE_ACTIVE
+            if (controlsDialog == null) {
+                mUiHandler.post(this::createDialog)
+            }
+        } else {
+            state.state = Tile.STATE_UNAVAILABLE
+            dismissDialog()
+        }
+    }
+
+    override fun getMetricsCategory(): Int {
+        return 0
+    }
+
+    override fun getLongClickIntent(): Intent {
+        return intent
+    }
+
+    override fun getTileLabel(): CharSequence {
+        return mContext.getText(R.string.quick_controls_title)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
index 6a8c6149..6ca550c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
@@ -41,7 +41,7 @@
     protected static int layoutResId = R.layout.qs_user_detail_item;
 
     private UserAvatarView mAvatar;
-    private TextView mName;
+    protected TextView mName;
     private int mActivatedStyle;
     private int mRegularStyle;
     private View mRestrictedPadlock;
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index 5438743..26781f4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -277,12 +277,12 @@
      */
     void end() {
         mMediaRecorder.stop();
-        mMediaProjection.stop();
         mMediaRecorder.release();
-        mMediaRecorder = null;
-        mMediaProjection = null;
         mInputSurface.release();
         mVirtualDisplay.release();
+        mMediaProjection.stop();
+        mMediaRecorder = null;
+        mMediaProjection = null;
         stopInternalAudioRecording();
 
         Log.d(TAG, "end recording");
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
index c8afd0b..9383aef 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
@@ -16,17 +16,22 @@
 
 package com.android.systemui.screenshot;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.MathUtils;
 import android.view.MotionEvent;
 import android.view.View;
 
 import androidx.annotation.Nullable;
+import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
 
 import com.android.systemui.R;
 
@@ -35,6 +40,7 @@
  * cropped out.
  */
 public class CropView extends View {
+    private static final String TAG = "CropView";
     public enum CropBoundary {
         NONE, TOP, BOTTOM
     }
@@ -118,10 +124,7 @@
             case MotionEvent.ACTION_UP:
                 if (mCurrentDraggingBoundary != CropBoundary.NONE) {
                     // Commit the delta to the stored crop values.
-                    mTopCrop += mTopDelta;
-                    mBottomCrop += mBottomDelta;
-                    mTopDelta = 0;
-                    mBottomDelta = 0;
+                    commitDeltas();
                     updateListener(event);
                 }
         }
@@ -129,6 +132,42 @@
     }
 
     /**
+     * Animate the given boundary to the given value.
+     */
+    public void animateBoundaryTo(CropBoundary boundary, float value) {
+        if (boundary == CropBoundary.NONE) {
+            Log.w(TAG, "No boundary selected for animation");
+            return;
+        }
+        float totalDelta = (boundary == CropBoundary.TOP) ? (value - mTopCrop)
+                : (value - mBottomCrop);
+        ValueAnimator animator = new ValueAnimator();
+        animator.addUpdateListener(animation -> {
+            if (boundary == CropBoundary.TOP) {
+                mTopDelta = animation.getAnimatedFraction() * totalDelta;
+            } else {
+                mBottomDelta = animation.getAnimatedFraction() * totalDelta;
+            }
+            invalidate();
+        });
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                commitDeltas();
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                commitDeltas();
+            }
+        });
+        animator.setFloatValues(0f, 1f);
+        animator.setDuration(750);
+        animator.setInterpolator(new FastOutSlowInInterpolator());
+        animator.start();
+    }
+
+    /**
      * @return value [0,1] representing the position of the top crop boundary. Does not reflect
      * changes from any in-progress touch input.
      */
@@ -148,6 +187,13 @@
         mCropInteractionListener = listener;
     }
 
+    private void commitDeltas() {
+        mTopCrop += mTopDelta;
+        mBottomCrop += mBottomDelta;
+        mTopDelta = 0;
+        mBottomDelta = 0;
+    }
+
     private void updateListener(MotionEvent event) {
         if (mCropInteractionListener != null) {
             float boundaryPosition = (mCurrentDraggingBoundary == CropBoundary.TOP)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 25438a6..a78126e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -277,6 +277,7 @@
         } else {
             mPreview.setImageDrawable(mImageTileSet.getDrawable());
             mMagnifierView.setImageTileset(mImageTileSet);
+            mCropView.animateBoundaryTo(CropView.CropBoundary.BOTTOM, 0.5f);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 43bb343..0bfc8e5 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -41,7 +41,7 @@
 import android.util.Log;
 import android.util.MathUtils;
 
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.RestrictedLockUtilsInternal;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index 2dd85e9..862c279 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -74,4 +74,12 @@
     public boolean isPeopleTileEnabled() {
         return mFlagReader.isEnabled(R.bool.flag_conversations);
     }
+
+    public boolean isToastStyleEnabled() {
+        return mFlagReader.isEnabled(R.bool.flag_toast_style);
+    }
+
+    public boolean isMonetEnabled() {
+        return mFlagReader.isEnabled(R.bool.flag_monet);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 57a64e4..e0df4f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -27,6 +27,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView;
 
 /**
  * Utility class to calculate the clock position and top padding of notifications on Keyguard.
@@ -55,6 +56,12 @@
     private int mKeyguardStatusHeight;
 
     /**
+     * Height of {@link KeyguardUserSwitcherListView} when it
+     * is closed and only the current user's icon is visible.
+     */
+    private int mKeyguardUserSwitcherHeight;
+
+    /**
      * Preferred Y position of clock.
      */
     private int mClockPreferredY;
@@ -173,17 +180,20 @@
      * Sets up algorithm values.
      */
     public void setup(int statusBarMinHeight, int maxShadeBottom, int notificationStackHeight,
-            float panelExpansion, int parentHeight, int keyguardStatusHeight, int clockPreferredY,
-            boolean hasCustomClock, boolean hasVisibleNotifs, float dark, float emptyDragAmount,
-            boolean bypassEnabled, int unlockedStackScrollerPadding, boolean showLockIcon,
-            float qsExpansion, int cutoutTopInset) {
+            float panelExpansion, int parentHeight, int keyguardStatusHeight,
+            int keyguardUserSwitcherHeight, int clockPreferredY, boolean hasCustomClock,
+            boolean hasVisibleNotifs, float dark, float emptyDragAmount, boolean bypassEnabled,
+            int unlockedStackScrollerPadding, boolean showLockIcon, float qsExpansion,
+            int cutoutTopInset) {
         mMinTopMargin = statusBarMinHeight + (showLockIcon
-                ? mContainerTopPaddingWithLockIcon : mContainerTopPaddingWithoutLockIcon);
+                ? mContainerTopPaddingWithLockIcon : mContainerTopPaddingWithoutLockIcon)
+                + keyguardUserSwitcherHeight;
         mMaxShadeBottom = maxShadeBottom;
         mNotificationStackHeight = notificationStackHeight;
         mPanelExpansion = panelExpansion;
         mHeight = parentHeight;
         mKeyguardStatusHeight = keyguardStatusHeight;
+        mKeyguardUserSwitcherHeight = keyguardUserSwitcherHeight;
         mClockPreferredY = clockPreferredY;
         mHasCustomClock = hasCustomClock;
         mHasVisibleNotifs = hasVisibleNotifs;
@@ -246,7 +256,8 @@
         final int availableHeight = mMaxShadeBottom - mMinTopMargin;
         final int containerCenter = mMinTopMargin + availableHeight / 2;
 
-        float y = containerCenter - mKeyguardStatusHeight * CLOCK_HEIGHT_WEIGHT
+        float y = containerCenter
+                - (mKeyguardStatusHeight + mKeyguardUserSwitcherHeight) * CLOCK_HEIGHT_WEIGHT
                 - mClockNotificationsMargin - mNotificationStackHeight / 2;
         if (y < mMinTopMargin) {
             y = mMinTopMargin;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 5f547b5..33798d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.DejankUtils.whitelistIpcs;
 import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
 
 import android.annotation.ColorInt;
@@ -25,6 +26,7 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.UserManager;
 import android.util.AttributeSet;
 import android.util.Pair;
 import android.util.TypedValue;
@@ -45,18 +47,15 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.qs.QSDetailDisplayer;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -75,18 +74,16 @@
 
     private boolean mShowPercentAvailable;
     private boolean mBatteryCharging;
-    private boolean mKeyguardUserSwitcherShowing;
     private boolean mBatteryListening;
 
     private TextView mCarrierLabel;
-    private MultiUserSwitch mMultiUserSwitch;
     private ImageView mMultiUserAvatar;
     private BatteryMeterView mBatteryView;
     private StatusIconContainer mStatusIconContainer;
 
     private BatteryController mBatteryController;
-    private KeyguardUserSwitcher mKeyguardUserSwitcher;
-    private UserSwitcherController mUserSwitcherController;
+    private boolean mKeyguardUserSwitcherEnabled;
+    private final UserManager mUserManager;
 
     private int mSystemIconsSwitcherHiddenExpandedMargin;
     private int mSystemIconsBaseMargin;
@@ -109,13 +106,13 @@
 
     public KeyguardStatusBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mUserManager = UserManager.get(getContext());
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
         mSystemIconsContainer = findViewById(R.id.system_icons_container);
-        mMultiUserSwitch = findViewById(R.id.multi_user_switch);
         mMultiUserAvatar = findViewById(R.id.multi_user_avatar);
         mCarrierLabel = findViewById(R.id.keyguard_carrier_text);
         mBatteryView = mSystemIconsContainer.findViewById(R.id.battery);
@@ -124,7 +121,6 @@
         mStatusIconContainer = findViewById(R.id.statusIcons);
 
         loadDimens();
-        updateUserSwitcher();
         mBatteryController = Dependency.get(BatteryController.class);
     }
 
@@ -137,14 +133,6 @@
                 R.dimen.multi_user_avatar_keyguard_size);
         mMultiUserAvatar.setLayoutParams(lp);
 
-        // Multi-user switch
-        lp = (MarginLayoutParams) mMultiUserSwitch.getLayoutParams();
-        lp.width = getResources().getDimensionPixelSize(
-                R.dimen.multi_user_switch_width_keyguard);
-        lp.setMarginEnd(getResources().getDimensionPixelSize(
-                R.dimen.multi_user_switch_keyguard_margin));
-        mMultiUserSwitch.setLayoutParams(lp);
-
         // System icons
         lp = (MarginLayoutParams) mSystemIconsContainer.getLayoutParams();
         lp.setMarginStart(getResources().getDimensionPixelSize(
@@ -194,22 +182,28 @@
     }
 
     private void updateVisibilities() {
-        if (mMultiUserSwitch.getParent() != mStatusIconArea && !mKeyguardUserSwitcherShowing) {
-            if (mMultiUserSwitch.getParent() != null) {
-                getOverlay().remove(mMultiUserSwitch);
+        if (mMultiUserAvatar.getParent() != mStatusIconArea
+                && !mKeyguardUserSwitcherEnabled) {
+            if (mMultiUserAvatar.getParent() != null) {
+                getOverlay().remove(mMultiUserAvatar);
             }
-            mStatusIconArea.addView(mMultiUserSwitch, 0);
-        } else if (mMultiUserSwitch.getParent() == mStatusIconArea && mKeyguardUserSwitcherShowing) {
-            mStatusIconArea.removeView(mMultiUserSwitch);
+            mStatusIconArea.addView(mMultiUserAvatar, 0);
+        } else if (mMultiUserAvatar.getParent() == mStatusIconArea
+                && mKeyguardUserSwitcherEnabled) {
+            mStatusIconArea.removeView(mMultiUserAvatar);
         }
-        if (mKeyguardUserSwitcher == null) {
+        if (!mKeyguardUserSwitcherEnabled) {
             // If we have no keyguard switcher, the screen width is under 600dp. In this case,
             // we only show the multi-user switch if it's enabled through UserManager as well as
             // by the user.
-            if (mMultiUserSwitch.isMultiUserEnabled()) {
-                mMultiUserSwitch.setVisibility(View.VISIBLE);
+            // TODO(b/138661450) Move IPC calls to background
+            boolean isMultiUserEnabled = whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled(
+                    mContext.getResources().getBoolean(
+                            R.bool.qs_show_user_switcher_for_single_user)));
+            if (isMultiUserEnabled) {
+                mMultiUserAvatar.setVisibility(View.VISIBLE);
             } else {
-                mMultiUserSwitch.setVisibility(View.GONE);
+                mMultiUserAvatar.setVisibility(View.GONE);
             }
         }
         mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable);
@@ -220,11 +214,12 @@
                 (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
         // If the avatar icon is gone, we need to have some end margin to display the system icons
         // correctly.
-        int baseMarginEnd = mMultiUserSwitch.getVisibility() == View.GONE
+        int baseMarginEnd = mMultiUserAvatar.getVisibility() == View.GONE
                 ? mSystemIconsBaseMargin
                 : 0;
-        int marginEnd = mKeyguardUserSwitcherShowing ? mSystemIconsSwitcherHiddenExpandedMargin :
-                baseMarginEnd;
+        int marginEnd =
+                mKeyguardUserSwitcherEnabled ? mSystemIconsSwitcherHiddenExpandedMargin
+                        : baseMarginEnd;
         marginEnd = calculateMargin(marginEnd, mPadding.second);
         if (marginEnd != lp.getMarginEnd()) {
             lp.setMarginEnd(marginEnd);
@@ -334,20 +329,11 @@
         }
     }
 
-    private void updateUserSwitcher() {
-        boolean keyguardSwitcherAvailable = mKeyguardUserSwitcher != null;
-        mMultiUserSwitch.setClickable(keyguardSwitcherAvailable);
-        mMultiUserSwitch.setFocusable(keyguardSwitcherAvailable);
-        mMultiUserSwitch.setKeyguardMode(keyguardSwitcherAvailable);
-    }
-
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         UserInfoController userInfoController = Dependency.get(UserInfoController.class);
         userInfoController.addCallback(this);
-        mUserSwitcherController = Dependency.get(UserSwitcherController.class);
-        mMultiUserSwitch.setUserSwitcherController(mUserSwitcherController);
         userInfoController.reloadUserInfo();
         Dependency.get(ConfigurationController.class).addCallback(this);
         mIconManager = new TintedIconManager(findViewById(R.id.statusIcons),
@@ -369,11 +355,6 @@
         mMultiUserAvatar.setImageDrawable(picture);
     }
 
-    /** */
-    public void setQSDetailDisplayer(QSDetailDisplayer detailDisplayer) {
-        mMultiUserSwitch.setQSDetailDisplayer(detailDisplayer);
-    }
-
     @Override
     public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
         if (mBatteryCharging != charging) {
@@ -387,54 +368,42 @@
         // could not care less
     }
 
-    public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
-        mKeyguardUserSwitcher = keyguardUserSwitcher;
-        mMultiUserSwitch.setKeyguardUserSwitcher(keyguardUserSwitcher);
-        updateUserSwitcher();
-    }
-
-    public void setKeyguardUserSwitcherShowing(boolean showing, boolean animate) {
-        mKeyguardUserSwitcherShowing = showing;
-        if (animate) {
-            animateNextLayoutChange();
-        }
-        updateVisibilities();
-        updateLayoutConsideringCutout();
-        updateSystemIconsLayoutParams();
+    public void setKeyguardUserSwitcherEnabled(boolean enabled) {
+        mKeyguardUserSwitcherEnabled = enabled;
     }
 
     private void animateNextLayoutChange() {
         final int systemIconsCurrentX = mSystemIconsContainer.getLeft();
-        final boolean userSwitcherVisible = mMultiUserSwitch.getParent() == mStatusIconArea;
+        final boolean userAvatarVisible = mMultiUserAvatar.getParent() == mStatusIconArea;
         getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
             @Override
             public boolean onPreDraw() {
                 getViewTreeObserver().removeOnPreDrawListener(this);
-                boolean userSwitcherHiding = userSwitcherVisible
-                        && mMultiUserSwitch.getParent() != mStatusIconArea;
+                boolean userAvatarHiding = userAvatarVisible
+                        && mMultiUserAvatar.getParent() != mStatusIconArea;
                 mSystemIconsContainer.setX(systemIconsCurrentX);
                 mSystemIconsContainer.animate()
                         .translationX(0)
                         .setDuration(400)
-                        .setStartDelay(userSwitcherHiding ? 300 : 0)
+                        .setStartDelay(userAvatarHiding ? 300 : 0)
                         .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
                         .start();
-                if (userSwitcherHiding) {
-                    getOverlay().add(mMultiUserSwitch);
-                    mMultiUserSwitch.animate()
+                if (userAvatarHiding) {
+                    getOverlay().add(mMultiUserAvatar);
+                    mMultiUserAvatar.animate()
                             .alpha(0f)
                             .setDuration(300)
                             .setStartDelay(0)
                             .setInterpolator(Interpolators.ALPHA_OUT)
                             .withEndAction(() -> {
-                                mMultiUserSwitch.setAlpha(1f);
-                                getOverlay().remove(mMultiUserSwitch);
+                                mMultiUserAvatar.setAlpha(1f);
+                                getOverlay().remove(mMultiUserAvatar);
                             })
                             .start();
 
                 } else {
-                    mMultiUserSwitch.setAlpha(0f);
-                    mMultiUserSwitch.animate()
+                    mMultiUserAvatar.setAlpha(0f);
+                    mMultiUserAvatar.animate()
                             .alpha(1f)
                             .setDuration(300)
                             .setStartDelay(200)
@@ -452,8 +421,8 @@
         if (visibility != View.VISIBLE) {
             mSystemIconsContainer.animate().cancel();
             mSystemIconsContainer.setTranslationX(0);
-            mMultiUserSwitch.animate().cancel();
-            mMultiUserSwitch.setAlpha(1f);
+            mMultiUserAvatar.animate().cancel();
+            mMultiUserAvatar.setAlpha(1f);
         } else {
             updateVisibilities();
             updateSystemIconsLayoutParams();
@@ -523,9 +492,9 @@
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("KeyguardStatusBarView:");
         pw.println("  mBatteryCharging: " + mBatteryCharging);
-        pw.println("  mKeyguardUserSwitcherShowing: " + mKeyguardUserSwitcherShowing);
         pw.println("  mBatteryListening: " + mBatteryListening);
         pw.println("  mLayoutState: " + mLayoutState);
+        pw.println("  mKeyguardUserSwitcherEnabled: " + mKeyguardUserSwitcherEnabled);
         if (mBatteryView != null) {
             mBatteryView.dump(fd, pw, args);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index 480d3f4..d9cb9ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -35,7 +35,6 @@
 import com.android.systemui.R;
 import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.qs.QSDetailDisplayer;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 
 /**
@@ -44,8 +43,6 @@
 public class MultiUserSwitch extends FrameLayout implements View.OnClickListener {
 
     protected QSDetailDisplayer mQSDetailDisplayer;
-    private KeyguardUserSwitcher mKeyguardUserSwitcher;
-    private boolean mKeyguardMode;
     private UserSwitcherController.BaseUserAdapter mUserListener;
 
     final UserManager mUserManager;
@@ -85,15 +82,6 @@
         refreshContentDescription();
     }
 
-    public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
-        mKeyguardUserSwitcher = keyguardUserSwitcher;
-    }
-
-    public void setKeyguardMode(boolean keyguardShowing) {
-        mKeyguardMode = keyguardShowing;
-        registerListener();
-    }
-
     public boolean isMultiUserEnabled() {
         // TODO(b/138661450) Move IPC calls to background
         return whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled(
@@ -123,11 +111,7 @@
 
     @Override
     public void onClick(View v) {
-        if (mKeyguardMode) {
-            if (mKeyguardUserSwitcher != null) {
-                mKeyguardUserSwitcher.show(true /* animate */);
-            }
-        } else if (mQSDetailDisplayer != null && mUserSwitcherController != null) {
+        if (mQSDetailDisplayer != null && mUserSwitcherController != null) {
             View center = getChildCount() > 0 ? getChildAt(0) : this;
 
             int[] tmpInt = new int[2];
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index e0ef3b6..3b09eda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -48,6 +48,7 @@
 import android.os.Bundle;
 import android.os.PowerManager;
 import android.os.SystemClock;
+import android.os.UserManager;
 import android.util.Log;
 import android.util.MathUtils;
 import android.view.DisplayCutout;
@@ -57,6 +58,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewPropertyAnimator;
+import android.view.ViewStub;
 import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.accessibility.AccessibilityManager;
@@ -76,6 +78,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
+import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
@@ -95,7 +98,6 @@
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.qs.QSDetailDisplayer;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.GestureRecorder;
@@ -132,8 +134,10 @@
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.util.Utils;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 
 import java.io.FileDescriptor;
@@ -283,6 +287,22 @@
                 }
     };
 
+    final KeyguardUserSwitcherController.KeyguardUserSwitcherListener
+            mKeyguardUserSwitcherListener =
+            new KeyguardUserSwitcherController.KeyguardUserSwitcherListener() {
+                @Override
+                public void onKeyguardUserSwitcherChanged(boolean open) {
+                    if (mKeyguardUserSwitcherController != null
+                            && mKeyguardUserSwitcherController.isSimpleUserSwitcher()) {
+                        return;
+                    }
+
+                    updateUserSwitcherVisibility(open
+                            && mKeyguardStateController.isShowing()
+                            && !mKeyguardStateController.isKeyguardFadingAway());
+                }
+            };
+
     private final LayoutInflater mLayoutInflater;
     private final PowerManager mPowerManager;
     private final AccessibilityManager mAccessibilityManager;
@@ -295,7 +315,7 @@
     private final MediaHierarchyManager mMediaHierarchyManager;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
-    private final QSDetailDisplayer mQSDetailDisplayer;
+    private final KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory;
     private final FeatureFlags mFeatureFlags;
     private final ScrimController mScrimController;
     private final ControlsComponent mControlsComponent;
@@ -307,7 +327,7 @@
     private int mMaxAllowedKeyguardNotifications;
 
     private KeyguardAffordanceHelper mAffordanceHelper;
-    private KeyguardUserSwitcher mKeyguardUserSwitcher;
+    private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
     private KeyguardStatusBarView mKeyguardStatusBar;
     private ViewGroup mBigClockContainer;
     private QS mQs;
@@ -332,6 +352,7 @@
     private boolean mQsExpandedWhenExpandingStarted;
     private boolean mQsFullyExpanded;
     private boolean mKeyguardShowing;
+    private boolean mKeyguardUserSwitcherEnabled;
     private boolean mDozing;
     private boolean mDozingOnDown;
     private int mBarState;
@@ -464,6 +485,7 @@
 
     private final CommandQueue mCommandQueue;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
+    private final UserManager mUserManager;
     private final ShadeController mShadeController;
     private final MediaDataManager mMediaDataManager;
     private int mDisplayId;
@@ -556,11 +578,12 @@
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             NotificationStackScrollLayoutController notificationStackScrollLayoutController,
             KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
+            KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory,
             NotificationGroupManagerLegacy groupManager,
             NotificationIconAreaController notificationIconAreaController,
             AuthController authController,
-            QSDetailDisplayer qsDetailDisplayer,
             ScrimController scrimController,
+            UserManager userManager,
             MediaDataManager mediaDataManager,
             AmbientState ambientState,
             FeatureFlags featureFlags,
@@ -581,8 +604,10 @@
         mGroupManager = groupManager;
         mNotificationIconAreaController = notificationIconAreaController;
         mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
-        mQSDetailDisplayer = qsDetailDisplayer;
         mFeatureFlags = featureFlags;
+        mKeyguardUserSwitcherComponentFactory = keyguardUserSwitcherComponentFactory;
+        mKeyguardUserSwitcherEnabled = mResources.getBoolean(
+                com.android.internal.R.bool.config_keyguardUserSwitcher);
         mView.setWillNotDraw(!DEBUG);
         mLayoutInflater = layoutInflater;
         mFalsingManager = falsingManager;
@@ -598,6 +623,7 @@
         mDozeParameters = dozeParameters;
         mBiometricUnlockController = biometricUnlockController;
         mScrimController = scrimController;
+        mUserManager = userManager;
         mMediaDataManager = mediaDataManager;
         mControlsComponent = controlsComponent;
         pulseExpansionHandler.setPulseExpandAbortListener(() -> {
@@ -660,9 +686,17 @@
     private void onFinishInflate() {
         loadDimens();
         mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
-        mKeyguardStatusBar.setQSDetailDisplayer(mQSDetailDisplayer);
         mBigClockContainer = mView.findViewById(R.id.big_clock_container);
-        updateViewControllers(mView.findViewById(R.id.keyguard_status_view));
+
+        KeyguardUserSwitcherView keyguardUserSwitcherView = null;
+
+        if (mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled()) {
+            ViewStub userSwitcherStub = mView.findViewById(R.id.keyguard_user_switcher_stub);
+            keyguardUserSwitcherView = (KeyguardUserSwitcherView) userSwitcherStub.inflate();
+        }
+
+        updateViewControllers(mView.findViewById(R.id.keyguard_status_view),
+                keyguardUserSwitcherView);
         mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
         NotificationStackScrollLayout stackScrollLayout = mView.findViewById(
                 R.id.notification_stack_scroller);
@@ -735,7 +769,8 @@
                 R.dimen.heads_up_status_bar_padding);
     }
 
-    private void updateViewControllers(KeyguardStatusView keyguardStatusView) {
+    private void updateViewControllers(KeyguardStatusView keyguardStatusView,
+            KeyguardUserSwitcherView keyguardUserSwitcherView) {
         // Re-associate the KeyguardStatusViewController
         KeyguardStatusViewComponent statusViewComponent =
                 mKeyguardStatusViewComponentFactory.build(keyguardStatusView);
@@ -746,6 +781,28 @@
         KeyguardClockSwitchController keyguardClockSwitchController =
                 statusViewComponent.getKeyguardClockSwitchController();
         keyguardClockSwitchController.setBigClockContainer(mBigClockContainer);
+
+        if (mKeyguardUserSwitcherController != null) {
+            // Try to close the switcher so that callbacks are triggered if necessary.
+            // Otherwise, NPV can get into a state where some of the views are still hidden
+            mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(false);
+            mKeyguardUserSwitcherController.removeCallback();
+        }
+
+        // Re-associate the KeyguardUserSwitcherController
+        if (keyguardUserSwitcherView != null) {
+            KeyguardUserSwitcherComponent userSwitcherComponent =
+                    mKeyguardUserSwitcherComponentFactory.build(keyguardUserSwitcherView);
+
+            mKeyguardUserSwitcherController =
+                    userSwitcherComponent.getKeyguardUserSwitcherController();
+            mKeyguardUserSwitcherController.setCallback(mKeyguardUserSwitcherListener);
+            mKeyguardUserSwitcherController.init();
+            mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(true);
+        } else {
+            mKeyguardUserSwitcherController = null;
+            mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(false);
+        }
     }
 
     /**
@@ -783,18 +840,13 @@
             mNotificationStackScrollLayoutController.setLayoutParams(lp);
         }
 
-        if (shouldUseSplitNotificationShade()) {
+        if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) {
             // In order to change the constraints at runtime, all children of the Constraint Layout
             // must have ids.
             ensureAllViewsHaveIds(mNotificationContainerParent);
         }
     }
 
-    private boolean shouldUseSplitNotificationShade() {
-        return mFeatureFlags.isTwoColumnNotificationShadeEnabled()
-                && mResources.getBoolean(R.bool.config_use_split_notification_shade);
-    }
-
     private static void ensureAllViewsHaveIds(ViewGroup parentView) {
         for (int i = 0; i < parentView.getChildCount(); i++) {
             View childView = parentView.getChildAt(i);
@@ -805,6 +857,7 @@
     }
 
     private void reInflateViews() {
+        if (DEBUG) Log.d(TAG, "reInflateViews");
         // Re-inflate the status view group.
         KeyguardStatusView keyguardStatusView = mView.findViewById(R.id.keyguard_status_view);
         int index = mView.indexOfChild(keyguardStatusView);
@@ -813,8 +866,27 @@
                 R.layout.keyguard_status_view, mView, false);
         mView.addView(keyguardStatusView, index);
 
+        // Re-inflate the keyguard user switcher group.
+        boolean showUserSwitcher =
+                mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled();
+        KeyguardUserSwitcherView keyguardUserSwitcherView = mView.findViewById(
+                R.id.keyguard_user_switcher_view);
+        if (keyguardUserSwitcherView != null) {
+            index = mView.indexOfChild(keyguardUserSwitcherView);
+            mView.removeView(keyguardUserSwitcherView);
+            if (showUserSwitcher) {
+                keyguardUserSwitcherView = (KeyguardUserSwitcherView) mLayoutInflater.inflate(
+                        R.layout.keyguard_user_switcher, mView, false);
+                mView.addView(keyguardUserSwitcherView, index);
+            }
+        } else if (showUserSwitcher) {
+            // It's possible the user switcher was never inflated if the configuration changed
+            ViewStub userSwitcherStub = mView.findViewById(R.id.keyguard_user_switcher_stub);
+            keyguardUserSwitcherView = (KeyguardUserSwitcherView) userSwitcherStub.inflate();
+        }
+
         mBigClockContainer.removeAllViews();
-        updateViewControllers(keyguardStatusView);
+        updateViewControllers(keyguardStatusView, keyguardUserSwitcherView);
 
         // Update keyguard bottom area
         index = mView.indexOfChild(mKeyguardBottomArea);
@@ -838,6 +910,13 @@
                 false,
                 false,
                 mBarState);
+        if (mKeyguardUserSwitcherController != null) {
+            mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility(
+                    mBarState,
+                    false,
+                    false,
+                    mBarState);
+        }
         setKeyguardBottomAreaVisibility(mBarState, false);
     }
 
@@ -942,6 +1021,8 @@
                             ? mKeyguardStatusViewController.getHeight()
                             : (int) (mKeyguardStatusViewController.getHeight()
                                     - mShelfHeight / 2.0f - mDarkIconSize / 2.0f),
+                    mKeyguardUserSwitcherController == null
+                            ? 0 : mKeyguardUserSwitcherController.getUserIconHeight(),
                     clockPreferredY, hasCustomClock(),
                     hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount,
                     bypassEnabled, getUnlockedStackScrollerPadding(),
@@ -952,6 +1033,13 @@
             mKeyguardStatusViewController.updatePosition(
                     mClockPositionResult.clockX, mClockPositionResult.clockY,
                     mClockPositionResult.clockScale, animateClock);
+            if (mKeyguardUserSwitcherController != null) {
+                mKeyguardUserSwitcherController.updatePosition(
+                        mClockPositionResult.clockX,
+                        mClockPositionResult.clockY
+                                - mKeyguardUserSwitcherController.getUserIconHeight(),
+                        animateClock);
+            }
             updateNotificationTranslucency();
             updateClock();
             stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
@@ -1092,6 +1180,9 @@
 
     private void updateClock() {
         mKeyguardStatusViewController.setAlpha(mClockPositionResult.clockAlpha);
+        if (mKeyguardUserSwitcherController != null) {
+            mKeyguardUserSwitcherController.setAlpha(mClockPositionResult.clockAlpha);
+        }
     }
 
     public void animateToFullShade(long delay) {
@@ -1773,8 +1864,9 @@
                 mBarState != KEYGUARD && (!mQsExpanded
                         || mQsExpansionFromOverscroll));
 
-        if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) {
-            mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
+        if (mKeyguardUserSwitcherController != null && mQsExpanded
+                && !mStackScrollerOverscrolling) {
+            mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(true);
         }
         if (mQs == null) return;
         mQs.setExpanded(mQsExpanded);
@@ -2608,10 +2700,6 @@
         }
     }
 
-    public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
-        mKeyguardUserSwitcher = keyguardUserSwitcher;
-    }
-
     public void onScreenTurningOn() {
         mKeyguardStatusViewController.dozeTimeTick();
     }
@@ -3078,6 +3166,13 @@
                 true /* keyguardFadingAway */,
                 false /* goingToFullShade */,
                 mBarState);
+        if (mKeyguardUserSwitcherController != null) {
+            mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility(
+                    mBarState,
+                    true /* keyguardFadingAway */,
+                    false /* goingToFullShade */,
+                    mBarState);
+        }
     }
 
     /**
@@ -3320,6 +3415,44 @@
         return mNotificationStackScrollLayoutController;
     }
 
+    /**
+     * Close the keyguard user switcher if it is open and capable of closing.
+     *
+     * Has no effect if user switcher isn't supported, if the user switcher is already closed, or
+     * if the user switcher uses "simple" mode. The simple user switcher cannot be closed.
+     *
+     * @return true if the keyguard user switcher was open, and is now closed
+     */
+    public boolean closeUserSwitcherIfOpen() {
+        if (mKeyguardUserSwitcherController != null) {
+            return mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(
+                    true /* animate */);
+        }
+        return false;
+    }
+
+    private void updateUserSwitcherVisibility(boolean open) {
+        if (open) {
+            animateKeyguardStatusBarOut();
+            mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
+                    mBarState,
+                    true /* keyguardFadingAway */,
+                    true /* goingToFullShade */,
+                    mBarState);
+            setKeyguardBottomAreaVisibility(mBarState, true);
+            mNotificationContainerParent.setVisibility(View.GONE);
+        } else {
+            animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+            mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
+                    StatusBarState.KEYGUARD,
+                    false,
+                    false,
+                    StatusBarState.SHADE_LOCKED);
+            setKeyguardBottomAreaVisibility(mBarState, false);
+            mNotificationContainerParent.setVisibility(View.VISIBLE);
+        }
+    }
+
     private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener {
         @Override
         public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
@@ -3620,6 +3753,7 @@
     private class ConfigurationListener implements ConfigurationController.ConfigurationListener {
         @Override
         public void onThemeChanged() {
+            if (DEBUG) Log.d(TAG, "onThemeChanged");
             final int themeResId = mView.getContext().getThemeResId();
             if (mThemeResId == themeResId) {
                 return;
@@ -3631,11 +3765,15 @@
 
         @Override
         public void onOverlayChanged() {
+            if (DEBUG) Log.d(TAG, "onOverlayChanged");
             reInflateViews();
         }
 
         @Override
-        public void onUiModeChanged() {}
+        public void onDensityOrFontScaleChanged() {
+            if (DEBUG) Log.d(TAG, "onDensityOrFontScaleChanged");
+            reInflateViews();
+        }
     }
 
     private class StatusBarStateListener implements StateListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index b367406..e394ebc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -22,8 +22,6 @@
 import android.graphics.Canvas;
 import android.util.AttributeSet;
 import android.view.View;
-import android.view.ViewStub;
-import android.view.ViewStub.OnInflateListener;
 import android.view.WindowInsets;
 import android.widget.FrameLayout;
 
@@ -44,14 +42,11 @@
  * The container with notification stack scroller and quick settings inside.
  */
 public class NotificationsQuickSettingsContainer extends ConstraintLayout
-        implements OnInflateListener, FragmentListener,
-        AboveShelfObserver.HasViewAboveShelfChangedListener {
+        implements FragmentListener, AboveShelfObserver.HasViewAboveShelfChangedListener {
 
     private FrameLayout mQsFrame;
-    private View mUserSwitcher;
     private NotificationStackScrollLayout mStackScroller;
     private View mKeyguardStatusBar;
-    private boolean mInflated;
     private boolean mQsExpanded;
     private boolean mCustomizerAnimating;
 
@@ -73,9 +68,6 @@
         mStackScroller = findViewById(R.id.notification_stack_scroller);
         mStackScrollerMargin = ((LayoutParams) mStackScroller.getLayoutParams()).bottomMargin;
         mKeyguardStatusBar = findViewById(R.id.keyguard_header);
-        ViewStub userSwitcher = findViewById(R.id.keyguard_user_switcher);
-        userSwitcher.setOnInflateListener(this);
-        mUserSwitcher = userSwitcher;
     }
 
     @Override
@@ -119,10 +111,6 @@
         // touches first but the panel gets drawn above.
         mDrawingOrderedChildren.clear();
         mLayoutDrawingOrder.clear();
-        if (mInflated && mUserSwitcher.getVisibility() == View.VISIBLE) {
-            mDrawingOrderedChildren.add(mUserSwitcher);
-            mLayoutDrawingOrder.add(mUserSwitcher);
-        }
         if (mKeyguardStatusBar.getVisibility() == View.VISIBLE) {
             mDrawingOrderedChildren.add(mKeyguardStatusBar);
             mLayoutDrawingOrder.add(mKeyguardStatusBar);
@@ -158,14 +146,6 @@
     }
 
     @Override
-    public void onInflate(ViewStub stub, View inflated) {
-        if (stub == mUserSwitcher) {
-            mUserSwitcher = inflated;
-            mInflated = true;
-        }
-    }
-
-    @Override
     public void onFragmentViewCreated(String tag, Fragment fragment) {
         QS container = (QS) fragment;
         container.setContainer(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 1e19bee..041a97e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -89,7 +89,6 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.provider.Settings;
@@ -226,7 +225,6 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 import com.android.systemui.statusbar.policy.ExtensionController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
@@ -623,7 +621,6 @@
         }
     };
 
-    private KeyguardUserSwitcher mKeyguardUserSwitcher;
     private final UserSwitcherController mUserSwitcherController;
     private final NetworkController mNetworkController;
     private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
@@ -1212,9 +1209,6 @@
         });
 
         mNotificationPanelViewController.setUserSetupComplete(mUserSetup);
-        if (UserManager.get(mContext).isUserSwitcherEnabled()) {
-            createUserSwitcher();
-        }
 
         // Set up the quick settings tile panel
         final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
@@ -1441,9 +1435,6 @@
         // TODO: Bring these out of StatusBar.
         mUserInfoControllerImpl.onDensityOrFontScaleChanged();
         mUserSwitcherController.onDensityOrFontScaleChanged();
-        if (mKeyguardUserSwitcher != null) {
-            mKeyguardUserSwitcher.onDensityOrFontScaleChanged();
-        }
         mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
         mHeadsUpManager.onDensityOrFontScaleChanged();
     }
@@ -1477,13 +1468,6 @@
         }
     }
 
-    protected void createUserSwitcher() {
-        mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
-                mNotificationShadeWindowView.findViewById(R.id.keyguard_user_switcher),
-                mNotificationShadeWindowView.findViewById(R.id.keyguard_header),
-                mNotificationPanelViewController);
-    }
-
     private void inflateStatusBarWindow() {
         mNotificationShadeWindowView = mSuperStatusBarViewFactory.getNotificationShadeWindowView();
         StatusBarComponent statusBarComponent = mStatusBarComponentBuilder.get()
@@ -3266,7 +3250,7 @@
         mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
         if (mUserSwitcherController != null && mUserSwitcherController.useFullscreenUserSwitcher()) {
             mStatusBarStateController.setState(StatusBarState.FULLSCREEN_USER_SWITCHER);
-        } else if (!mPulseExpansionHandler.isWakingToShadeLocked()){
+        } else if (!mPulseExpansionHandler.isWakingToShadeLocked()) {
             mStatusBarStateController.setState(StatusBarState.KEYGUARD);
         }
         updatePanelExpansionForKeyguard();
@@ -3565,15 +3549,15 @@
             }
             return true;
         }
+        if (mNotificationPanelViewController.closeUserSwitcherIfOpen()) {
+            return true;
+        }
         if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) {
             if (mNotificationPanelViewController.canPanelBeCollapsed()) {
                 mShadeController.animateCollapsePanels();
             }
             return true;
         }
-        if (mKeyguardUserSwitcher != null && mKeyguardUserSwitcher.hideIfNotSimple(true)) {
-            return true;
-        }
         return false;
     }
 
@@ -3622,20 +3606,8 @@
         updateTheme();
         mNavigationBarController.touchAutoDim(mDisplayId);
         Trace.beginSection("StatusBar#updateKeyguardState");
-        if (mState == StatusBarState.KEYGUARD) {
-            if (mKeyguardUserSwitcher != null) {
-                mKeyguardUserSwitcher.setKeyguard(true,
-                        mStatusBarStateController.fromShadeLocked());
-            }
-            if (mStatusBarView != null) mStatusBarView.removePendingHideExpandedRunnables();
-        } else {
-            if (mKeyguardUserSwitcher != null) {
-                mKeyguardUserSwitcher.setKeyguard(false,
-                        mStatusBarStateController.goingToFullShade() ||
-                                mState == StatusBarState.SHADE_LOCKED ||
-                                mStatusBarStateController.fromShadeLocked());
-            }
-
+        if (mState == StatusBarState.KEYGUARD && mStatusBarView != null) {
+            mStatusBarView.removePendingHideExpandedRunnables();
         }
         updateDozingState();
         checkBarModes();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
index 07433e1..0649478 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
@@ -17,8 +17,15 @@
 package com.android.systemui.statusbar.policy;
 
 import android.content.Context;
+import android.graphics.Color;
 import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
 
+import androidx.core.graphics.ColorUtils;
+
+import com.android.keyguard.KeyguardConstants;
+import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.qs.tiles.UserDetailItemView;
 
@@ -27,6 +34,14 @@
  */
 public class KeyguardUserDetailItemView extends UserDetailItemView {
 
+    private static final String TAG = "KeyguardUserDetailItemView";
+    private static final boolean DEBUG = KeyguardConstants.DEBUG;
+
+    private static final int ANIMATION_DURATION_FADE_NAME = 240;
+
+    private float mDarkAmount;
+    private int mTextColor;
+
     public KeyguardUserDetailItemView(Context context) {
         this(context, null);
     }
@@ -48,4 +63,89 @@
     protected int getFontSizeDimen() {
         return R.dimen.kg_user_switcher_text_size;
     }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mTextColor = mName.getCurrentTextColor();
+        updateDark();
+    }
+
+    /**
+     * Update visibility of this view.
+     *
+     * @param showItem If true, this item is visible on the screen to the user. Generally this
+     *                 means that the item would be clickable. If false, item visibility will be
+     *                 set to GONE and hidden entirely.
+     * @param showTextName Whether or not the name should be shown next to the icon. If false,
+     *                     only the icon is shown.
+     * @param animate Whether the transition should be animated. Note, this only applies to
+     *                animating the text name. The item itself will not animate (i.e. fade in/out).
+     *                Instead, we delegate that to the parent view.
+     */
+    void updateVisibilities(boolean showItem, boolean showTextName, boolean animate) {
+        if (DEBUG) {
+            Log.d(TAG, String.format("updateVisibilities itemIsShown=%b nameIsShown=%b animate=%b",
+                    showItem, showTextName, animate));
+        }
+
+        getBackground().setAlpha((showItem && showTextName) ? 255 : 0);
+
+        if (showItem) {
+            if (showTextName) {
+                mName.setVisibility(View.VISIBLE);
+                if (animate) {
+                    mName.setAlpha(0f);
+                    mName.animate()
+                            .alpha(1f)
+                            .setDuration(ANIMATION_DURATION_FADE_NAME)
+                            .setInterpolator(Interpolators.ALPHA_IN);
+                } else {
+                    mName.setAlpha(1f);
+                }
+            } else {
+                if (animate) {
+                    mName.setVisibility(View.VISIBLE);
+                    mName.setAlpha(1f);
+                    mName.animate()
+                            .alpha(0f)
+                            .setDuration(ANIMATION_DURATION_FADE_NAME)
+                            .setInterpolator(Interpolators.ALPHA_OUT)
+                            .withEndAction(() -> {
+                                mName.setVisibility(View.GONE);
+                                mName.setAlpha(1f);
+                            });
+                } else {
+                    mName.setVisibility(View.GONE);
+                    mName.setAlpha(1f);
+                }
+            }
+            setVisibility(View.VISIBLE);
+            setAlpha(1f);
+        } else {
+            // If item isn't shown, don't animate. The parent class will animate the view instead
+            setVisibility(View.GONE);
+            setAlpha(1f);
+            mName.setVisibility(showTextName ? View.VISIBLE : View.GONE);
+            mName.setAlpha(1f);
+        }
+    }
+
+    /**
+     * Set the amount (ratio) that the device has transitioned to doze.
+     *
+     * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
+     */
+    public void setDarkAmount(float darkAmount) {
+        if (mDarkAmount == darkAmount) {
+            return;
+        }
+        mDarkAmount = darkAmount;
+        updateDark();
+    }
+
+    private void updateDark() {
+        final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
+        mName.setTextColor(blendedTextColor);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
deleted file mode 100644
index 90f5577..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ /dev/null
@@ -1,414 +0,0 @@
-/*
- * Copyright (C) 2014 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.policy;
-
-import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA;
-import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.database.DataSetObserver;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-import android.widget.FrameLayout;
-
-import com.android.settingslib.animation.AppearAnimationUtils;
-import com.android.settingslib.drawable.CircleFramedDrawable;
-import com.android.systemui.Dependency;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.qs.tiles.UserDetailItemView;
-import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
-
-import java.util.ArrayList;
-
-/**
- * Manages the user switcher on the Keyguard.
- */
-public class KeyguardUserSwitcher {
-
-    private static final String TAG = "KeyguardUserSwitcher";
-    private static final boolean ALWAYS_ON = false;
-
-    private final Container mUserSwitcherContainer;
-    private final KeyguardStatusBarView mStatusBarView;
-    private final KeyguardUserAdapter mAdapter;
-    private final AppearAnimationUtils mAppearAnimationUtils;
-    private final KeyguardUserSwitcherScrim mBackground;
-
-    private ViewGroup mUserSwitcher;
-    private ObjectAnimator mBgAnimator;
-    private UserSwitcherController mUserSwitcherController;
-    private boolean mAnimating;
-
-    public KeyguardUserSwitcher(Context context, ViewStub userSwitcher,
-            KeyguardStatusBarView statusBarView,
-            NotificationPanelViewController panelViewController) {
-        boolean keyguardUserSwitcherEnabled =
-                context.getResources().getBoolean(
-                        com.android.internal.R.bool.config_keyguardUserSwitcher) || ALWAYS_ON;
-        UserSwitcherController userSwitcherController = Dependency.get(UserSwitcherController.class);
-        if (userSwitcherController != null && keyguardUserSwitcherEnabled) {
-            mUserSwitcherContainer = (Container) userSwitcher.inflate();
-            mBackground = new KeyguardUserSwitcherScrim(context);
-            reinflateViews();
-            mStatusBarView = statusBarView;
-            mStatusBarView.setKeyguardUserSwitcher(this);
-            panelViewController.setKeyguardUserSwitcher(this);
-            mAdapter = new KeyguardUserAdapter(context, userSwitcherController, this);
-            mAdapter.registerDataSetObserver(mDataSetObserver);
-            mUserSwitcherController = userSwitcherController;
-            mAppearAnimationUtils = new AppearAnimationUtils(context, 400, -0.5f, 0.5f,
-                    Interpolators.FAST_OUT_SLOW_IN);
-            mUserSwitcherContainer.setKeyguardUserSwitcher(this);
-        } else {
-            mUserSwitcherContainer = null;
-            mStatusBarView = null;
-            mAdapter = null;
-            mAppearAnimationUtils = null;
-            mBackground = null;
-        }
-    }
-
-    private void reinflateViews() {
-        if (mUserSwitcher != null) {
-            mUserSwitcher.setBackground(null);
-            mUserSwitcher.removeOnLayoutChangeListener(mBackground);
-        }
-        mUserSwitcherContainer.removeAllViews();
-
-        LayoutInflater.from(mUserSwitcherContainer.getContext())
-                .inflate(R.layout.keyguard_user_switcher_inner, mUserSwitcherContainer);
-
-        mUserSwitcher = (ViewGroup) mUserSwitcherContainer.findViewById(
-                R.id.keyguard_user_switcher_inner);
-        mUserSwitcher.addOnLayoutChangeListener(mBackground);
-        mUserSwitcher.setBackground(mBackground);
-    }
-
-    public void setKeyguard(boolean keyguard, boolean animate) {
-        if (mUserSwitcher != null) {
-            if (keyguard && shouldExpandByDefault()) {
-                show(animate);
-            } else {
-                hide(animate);
-            }
-        }
-    }
-
-    /**
-     * @return true if the user switcher should be expanded by default on the lock screen.
-     * @see android.os.UserManager#isUserSwitcherEnabled()
-     */
-    private boolean shouldExpandByDefault() {
-        return (mUserSwitcherController != null) && mUserSwitcherController.isSimpleUserSwitcher();
-    }
-
-    public void show(boolean animate) {
-        if (mUserSwitcher != null && mUserSwitcherContainer.getVisibility() != View.VISIBLE) {
-            cancelAnimations();
-            mAdapter.refresh();
-            mUserSwitcherContainer.setVisibility(View.VISIBLE);
-            mStatusBarView.setKeyguardUserSwitcherShowing(true, animate);
-            if (animate) {
-                startAppearAnimation();
-            }
-        }
-    }
-
-    private boolean hide(boolean animate) {
-        if (mUserSwitcher != null && mUserSwitcherContainer.getVisibility() == View.VISIBLE) {
-            cancelAnimations();
-            if (animate) {
-                startDisappearAnimation();
-            } else {
-                mUserSwitcherContainer.setVisibility(View.GONE);
-            }
-            mStatusBarView.setKeyguardUserSwitcherShowing(false, animate);
-            return true;
-        }
-        return false;
-    }
-
-    private void cancelAnimations() {
-        int count = mUserSwitcher.getChildCount();
-        for (int i = 0; i < count; i++) {
-            mUserSwitcher.getChildAt(i).animate().cancel();
-        }
-        if (mBgAnimator != null) {
-            mBgAnimator.cancel();
-        }
-        mUserSwitcher.animate().cancel();
-        mAnimating = false;
-    }
-
-    private void startAppearAnimation() {
-        int count = mUserSwitcher.getChildCount();
-        View[] objects = new View[count];
-        for (int i = 0; i < count; i++) {
-            objects[i] = mUserSwitcher.getChildAt(i);
-        }
-        mUserSwitcher.setClipChildren(false);
-        mUserSwitcher.setClipToPadding(false);
-        mAppearAnimationUtils.startAnimation(objects, new Runnable() {
-            @Override
-            public void run() {
-                mUserSwitcher.setClipChildren(true);
-                mUserSwitcher.setClipToPadding(true);
-            }
-        });
-        mAnimating = true;
-        mBgAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 0, 255);
-        mBgAnimator.setDuration(400);
-        mBgAnimator.setInterpolator(Interpolators.ALPHA_IN);
-        mBgAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mBgAnimator = null;
-                mAnimating = false;
-            }
-        });
-        mBgAnimator.start();
-    }
-
-    private void startDisappearAnimation() {
-        mAnimating = true;
-        mUserSwitcher.animate()
-                .alpha(0f)
-                .setDuration(300)
-                .setInterpolator(Interpolators.ALPHA_OUT)
-                .withEndAction(new Runnable() {
-                    @Override
-                    public void run() {
-                        mUserSwitcherContainer.setVisibility(View.GONE);
-                        mUserSwitcher.setAlpha(1f);
-                        mAnimating = false;
-                    }
-                });
-    }
-
-    private void refresh() {
-        final int childCount = mUserSwitcher.getChildCount();
-        final int adapterCount = mAdapter.getCount();
-        final int N = Math.max(childCount, adapterCount);
-        for (int i = 0; i < N; i++) {
-            if (i < adapterCount) {
-                View oldView = null;
-                if (i < childCount) {
-                    oldView = mUserSwitcher.getChildAt(i);
-                }
-                View newView = mAdapter.getView(i, oldView, mUserSwitcher);
-                if (oldView == null) {
-                    // We ran out of existing views. Add it at the end.
-                    mUserSwitcher.addView(newView);
-                } else if (oldView != newView) {
-                    // We couldn't rebind the view. Replace it.
-                    mUserSwitcher.removeViewAt(i);
-                    mUserSwitcher.addView(newView, i);
-                }
-            } else {
-                int lastIndex = mUserSwitcher.getChildCount() - 1;
-                mUserSwitcher.removeViewAt(lastIndex);
-            }
-        }
-    }
-
-    public boolean hideIfNotSimple(boolean animate) {
-        if (mUserSwitcherContainer != null && !mUserSwitcherController.isSimpleUserSwitcher()) {
-            return hide(animate);
-        }
-        return false;
-    }
-
-    boolean isAnimating() {
-        return mAnimating;
-    }
-
-    public final DataSetObserver mDataSetObserver = new DataSetObserver() {
-        @Override
-        public void onChanged() {
-            refresh();
-        }
-    };
-
-    public void onDensityOrFontScaleChanged() {
-        if (mUserSwitcherContainer != null) {
-            reinflateViews();
-            refresh();
-        }
-    }
-
-    static class KeyguardUserAdapter extends
-            UserSwitcherController.BaseUserAdapter implements View.OnClickListener {
-
-        private Context mContext;
-        private KeyguardUserSwitcher mKeyguardUserSwitcher;
-        private View mCurrentUserView;
-        // List of users where the first entry is always the current user
-        private ArrayList<UserSwitcherController.UserRecord> mUsersOrdered = new ArrayList<>();
-
-        KeyguardUserAdapter(Context context, UserSwitcherController controller,
-                KeyguardUserSwitcher kgu) {
-            super(controller);
-            mContext = context;
-            mKeyguardUserSwitcher = kgu;
-        }
-
-        @Override
-        public void notifyDataSetChanged() {
-            refreshUserOrder();
-            super.notifyDataSetChanged();
-        }
-
-        void refreshUserOrder() {
-            ArrayList<UserSwitcherController.UserRecord> users = super.getUsers();
-            mUsersOrdered = new ArrayList<>(users.size());
-            for (int i = 0; i < users.size(); i++) {
-                UserSwitcherController.UserRecord record = users.get(i);
-                if (record.isCurrent) {
-                    mUsersOrdered.add(0, record);
-                } else {
-                    mUsersOrdered.add(record);
-                }
-            }
-        }
-
-        @Override
-        protected ArrayList<UserSwitcherController.UserRecord> getUsers() {
-            return mUsersOrdered;
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            UserSwitcherController.UserRecord item = getItem(position);
-            return createUserDetailItemView(convertView, parent, item);
-        }
-
-        KeyguardUserDetailItemView convertOrInflate(View convertView, ViewGroup parent) {
-            if (!(convertView instanceof KeyguardUserDetailItemView)
-                    || !(convertView.getTag() instanceof UserSwitcherController.UserRecord)) {
-                convertView = LayoutInflater.from(mContext).inflate(
-                        R.layout.keyguard_user_switcher_item, parent, false);
-            }
-            return (KeyguardUserDetailItemView) convertView;
-        }
-
-        UserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
-                UserSwitcherController.UserRecord item) {
-            KeyguardUserDetailItemView v = convertOrInflate(convertView, parent);
-            if (!item.isCurrent || item.isGuest) {
-                v.setOnClickListener(this);
-            } else {
-                v.setOnClickListener(null);
-                v.setClickable(false);
-            }
-
-            String name = getName(mContext, item);
-            if (item.picture == null) {
-                v.bind(name, getDrawable(mContext, item).mutate(), item.resolveId());
-            } else {
-                int avatarSize =
-                        (int) mContext.getResources().getDimension(R.dimen.kg_framed_avatar_size);
-                Drawable drawable = new CircleFramedDrawable(item.picture, avatarSize);
-                drawable.setColorFilter(
-                        item.isSwitchToEnabled ? null : getDisabledUserAvatarColorFilter());
-                v.bind(name, drawable, item.info.id);
-            }
-            v.setActivated(item.isCurrent);
-            v.setDisabledByAdmin(item.isDisabledByAdmin);
-            v.setEnabled(item.isSwitchToEnabled);
-            v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA);
-
-            if (item.isCurrent) {
-                mCurrentUserView = v;
-            }
-            v.setTag(item);
-            return v;
-        }
-
-        private static Drawable getDrawable(Context context,
-                UserSwitcherController.UserRecord item) {
-            Drawable drawable = getIconDrawable(context, item);
-            int iconColorRes;
-            if (item.isCurrent) {
-                iconColorRes = R.color.kg_user_switcher_selected_avatar_icon_color;
-            } else if (!item.isSwitchToEnabled) {
-                iconColorRes = R.color.GM2_grey_600;
-            } else {
-                iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
-            }
-            drawable.setTint(context.getResources().getColor(iconColorRes, context.getTheme()));
-
-            if (item.isCurrent) {
-                Drawable bg = context.getDrawable(R.drawable.bg_avatar_selected);
-                drawable = new LayerDrawable(new Drawable[]{bg, drawable});
-            }
-
-            return drawable;
-        }
-
-        @Override
-        public void onClick(View v) {
-            UserSwitcherController.UserRecord user = (UserSwitcherController.UserRecord) v.getTag();
-            if (user.isCurrent && !user.isGuest) {
-                // Close the switcher if tapping the current user. Guest is excluded because
-                // tapping the guest user while it's current clears the session.
-                mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
-            } else if (user.isSwitchToEnabled) {
-                if (!user.isAddUser && !user.isRestricted && !user.isDisabledByAdmin) {
-                    if (mCurrentUserView != null) {
-                        mCurrentUserView.setActivated(false);
-                    }
-                    v.setActivated(true);
-                }
-                onUserListItemClicked(user);
-            }
-        }
-    }
-
-    public static class Container extends FrameLayout {
-
-        private KeyguardUserSwitcher mKeyguardUserSwitcher;
-
-        public Container(Context context, AttributeSet attrs) {
-            super(context, attrs);
-            setClipChildren(false);
-        }
-
-        public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
-            mKeyguardUserSwitcher = keyguardUserSwitcher;
-        }
-
-        @Override
-        public boolean onTouchEvent(MotionEvent ev) {
-            // Hide switcher if it didn't handle the touch event (and let the event go through).
-            if (mKeyguardUserSwitcher != null && !mKeyguardUserSwitcher.isAnimating()) {
-                mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
-            }
-            return false;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
new file mode 100644
index 0000000..b76e451
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -0,0 +1,639 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA;
+import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.DataSetObserver;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.android.keyguard.KeyguardConstants;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.KeyguardVisibilityHelper;
+import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
+import com.android.settingslib.drawable.CircleFramedDrawable;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.util.ViewController;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+import javax.inject.Inject;
+
+/**
+ * Manages the user switcher on the Keyguard.
+ */
+@KeyguardUserSwitcherScope
+public class KeyguardUserSwitcherController extends ViewController<KeyguardUserSwitcherView> {
+
+    private static final String TAG = "KeyguardUserSwitcherController";
+    private static final boolean DEBUG = KeyguardConstants.DEBUG;
+
+    private static final AnimationProperties ANIMATION_PROPERTIES =
+            new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+
+    private final Context mContext;
+    private final UserSwitcherController mUserSwitcherController;
+    private final ScreenLifecycle mScreenLifecycle;
+    private final KeyguardUserAdapter mAdapter;
+    private final KeyguardStateController mKeyguardStateController;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private WeakReference<KeyguardUserSwitcherListener> mKeyguardUserSwitcherCallback;
+    protected final SysuiStatusBarStateController mStatusBarStateController;
+    private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
+
+    // Child views of KeyguardUserSwitcherView
+    private KeyguardUserSwitcherListView mListView;
+    private LinearLayout mEndGuestButton;
+
+    // State info for the user switcher
+    private boolean mUserSwitcherOpen;
+    private int mCurrentUserId = UserHandle.USER_NULL;
+    private boolean mCurrentUserIsGuest;
+    private int mBarState;
+    private float mDarkAmount;
+
+    private final KeyguardUpdateMonitorCallback mInfoCallback =
+            new KeyguardUpdateMonitorCallback() {
+                @Override
+                public void onKeyguardVisibilityChanged(boolean showing) {
+                    if (DEBUG) Log.d(TAG, String.format("onKeyguardVisibilityChanged %b", showing));
+                    // Any time the keyguard is hidden, try to close the user switcher menu to
+                    // restore keyguard to the default state
+                    if (!showing) {
+                        closeSwitcherIfOpenAndNotSimple(false);
+                    }
+                }
+
+                @Override
+                public void onUserSwitching(int userId) {
+                    closeSwitcherIfOpenAndNotSimple(false);
+                }
+            };
+
+    private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
+        @Override
+        public void onScreenTurnedOff() {
+            if (DEBUG) Log.d(TAG, "onScreenTurnedOff");
+            closeSwitcherIfOpenAndNotSimple(false);
+        }
+    };
+
+    private final StatusBarStateController.StateListener mStatusBarStateListener =
+            new StatusBarStateController.StateListener() {
+                @Override
+                public void onStateChanged(int newState) {
+                    if (DEBUG) Log.d(TAG, String.format("onStateChanged: newState=%d", newState));
+
+                    boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
+                    boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
+                    int oldState = mBarState;
+                    mBarState = newState;
+
+                    if (mStatusBarStateController.goingToFullShade()
+                            || mKeyguardStateController.isKeyguardFadingAway()) {
+                        closeSwitcherIfOpenAndNotSimple(true);
+                    }
+
+                    setKeyguardUserSwitcherVisibility(
+                            newState,
+                            keyguardFadingAway,
+                            goingToFullShade,
+                            oldState);
+                }
+
+                @Override
+                public void onDozeAmountChanged(float linearAmount, float amount) {
+                    if (DEBUG) {
+                        Log.d(TAG, String.format("onDozeAmountChanged: linearAmount=%f amount=%f",
+                                linearAmount, amount));
+                    }
+                    setDarkAmount(amount);
+                }
+            };
+
+    @Inject
+    public KeyguardUserSwitcherController(
+            KeyguardUserSwitcherView keyguardUserSwitcherView,
+            Context context,
+            @Main Resources resources,
+            LayoutInflater layoutInflater,
+            ScreenLifecycle screenLifecycle,
+            UserSwitcherController userSwitcherController,
+            KeyguardStateController keyguardStateController,
+            SysuiStatusBarStateController statusBarStateController,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            DozeParameters dozeParameters) {
+        super(keyguardUserSwitcherView);
+        if (DEBUG) Log.d(TAG, "New KeyguardUserSwitcherController");
+        mContext = context;
+        mScreenLifecycle = screenLifecycle;
+        mUserSwitcherController = userSwitcherController;
+        mKeyguardStateController = keyguardStateController;
+        mStatusBarStateController = statusBarStateController;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mAdapter = new KeyguardUserAdapter(mContext, resources, layoutInflater,
+                mUserSwitcherController, this);
+        mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
+                keyguardStateController, dozeParameters);
+    }
+
+    @Override
+    protected void onInit() {
+        super.onInit();
+
+        if (DEBUG) Log.d(TAG, "onInit");
+
+        mListView = mView.findViewById(R.id.keyguard_user_switcher_list);
+        mEndGuestButton = mView.findViewById(R.id.end_guest_button);
+
+        mEndGuestButton.setOnClickListener(v -> {
+            mUserSwitcherController.showExitGuestDialog(mCurrentUserId);
+        });
+
+        mView.setOnTouchListener((v, event) -> {
+            if (!isListAnimating()) {
+                // Hide switcher if it didn't handle the touch event (and block the event from
+                // going through).
+                return closeSwitcherIfOpenAndNotSimple(true);
+            }
+            return false;
+        });
+    }
+
+    @Override
+    protected void onViewAttached() {
+        if (DEBUG) Log.d(TAG, "onViewAttached");
+        mAdapter.registerDataSetObserver(mDataSetObserver);
+        mDataSetObserver.onChanged();
+        mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
+        mStatusBarStateController.addCallback(mStatusBarStateListener);
+        mScreenLifecycle.addObserver(mScreenObserver);
+    }
+
+    @Override
+    protected void onViewDetached() {
+        if (DEBUG) Log.d(TAG, "onViewDetached");
+
+        // Detaching the view will always close the switcher
+        closeSwitcherIfOpenAndNotSimple(false);
+
+        mAdapter.unregisterDataSetObserver(mDataSetObserver);
+        mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
+        mStatusBarStateController.removeCallback(mStatusBarStateListener);
+        mScreenLifecycle.removeObserver(mScreenObserver);
+    }
+
+    /**
+     * See:
+     *
+     * <ul>
+     *   <li>{@link com.android.internal.R.bool.config_expandLockScreenUserSwitcher}</li>
+     *    <li>{@link UserSwitcherController.SIMPLE_USER_SWITCHER_GLOBAL_SETTING}</li>
+     * </ul>
+     *
+     * @return true if the user switcher should be open by default on the lock screen.
+     * @see android.os.UserManager#isUserSwitcherEnabled()
+     */
+    public boolean isSimpleUserSwitcher() {
+        return mUserSwitcherController.isSimpleUserSwitcher();
+    }
+
+    /**
+     * @param animate if the transition should be animated
+     * @return true if the switcher state changed
+     */
+    public boolean closeSwitcherIfOpenAndNotSimple(boolean animate) {
+        if (isUserSwitcherOpen() && !isSimpleUserSwitcher()) {
+            setUserSwitcherOpened(false /* open */, animate);
+            return true;
+        }
+        return false;
+    }
+
+    public final DataSetObserver mDataSetObserver = new DataSetObserver() {
+        @Override
+        public void onChanged() {
+            refreshUserList();
+        }
+    };
+
+    void refreshUserList() {
+        final int childCount = mListView.getChildCount();
+        final int adapterCount = mAdapter.getCount();
+        final int count = Math.max(childCount, adapterCount);
+
+        if (DEBUG) {
+            Log.d(TAG, String.format("refreshUserList childCount=%d adapterCount=%d", childCount,
+                    adapterCount));
+        }
+
+        boolean foundCurrentUser = false;
+        for (int i = 0; i < count; i++) {
+            if (i < adapterCount) {
+                View oldView = null;
+                if (i < childCount) {
+                    oldView = mListView.getChildAt(i);
+                }
+                KeyguardUserDetailItemView newView = (KeyguardUserDetailItemView)
+                        mAdapter.getView(i, oldView, mListView);
+                UserSwitcherController.UserRecord userTag =
+                        (UserSwitcherController.UserRecord) newView.getTag();
+                if (userTag.isCurrent) {
+                    if (i != 0) {
+                        Log.w(TAG, "Current user is not the first view in the list");
+                    }
+                    foundCurrentUser = true;
+                    mCurrentUserId = userTag.info.id;
+                    mCurrentUserIsGuest = userTag.isGuest;
+                    // Current user is always visible
+                    newView.updateVisibilities(true /* showItem */,
+                            mUserSwitcherOpen /* showTextName */, false /* animate */);
+                } else {
+                    // Views for non-current users are always expanded (e.g. they should the name
+                    // next to the user icon). However, they could be hidden entirely if the list
+                    // is closed.
+                    newView.updateVisibilities(mUserSwitcherOpen /* showItem */,
+                            true /* showTextName */, false /* animate */);
+                }
+                newView.setDarkAmount(mDarkAmount);
+                if (oldView == null) {
+                    // We ran out of existing views. Add it at the end.
+                    mListView.addView(newView);
+                } else if (oldView != newView) {
+                    // We couldn't rebind the view. Replace it.
+                    mListView.replaceView(newView, i);
+                }
+            } else {
+                mListView.removeLastView();
+            }
+        }
+        if (!foundCurrentUser) {
+            Log.w(TAG, "Current user is not listed");
+            mCurrentUserId = UserHandle.USER_NULL;
+            mCurrentUserIsGuest = false;
+        }
+    }
+
+    /**
+     * Get the height of the keyguard user switcher view when closed.
+     */
+    public int getUserIconHeight() {
+        View firstChild = mListView.getChildAt(0);
+        return firstChild == null ? 0 : firstChild.getHeight();
+    }
+
+    /**
+     * Set the visibility of the keyguard user switcher view based on some new state.
+     */
+    public void setKeyguardUserSwitcherVisibility(
+            int statusBarState,
+            boolean keyguardFadingAway,
+            boolean goingToFullShade,
+            int oldStatusBarState) {
+        mKeyguardVisibilityHelper.setViewVisibility(
+                statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState);
+    }
+
+    /**
+     * Update position of the view with an optional animation
+     */
+    public void updatePosition(int x, int y, boolean animate) {
+        PropertyAnimator.setProperty(mListView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES,
+                animate);
+        PropertyAnimator.setProperty(mListView, AnimatableProperty.TRANSLATION_X, -Math.abs(x),
+                ANIMATION_PROPERTIES, animate);
+    }
+
+    /**
+     * Set keyguard user switcher view alpha.
+     */
+    public void setAlpha(float alpha) {
+        if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
+            mView.setAlpha(alpha);
+        }
+    }
+
+    /**
+     * Set the amount (ratio) that the device has transitioned to doze.
+     *
+     * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
+     */
+    private void setDarkAmount(float darkAmount) {
+        boolean isAwake = darkAmount != 0;
+        if (darkAmount == mDarkAmount) {
+            return;
+        }
+        mDarkAmount = darkAmount;
+        mListView.setDarkAmount(darkAmount);
+        mView.setVisibility(isAwake ? View.VISIBLE : View.GONE);
+        if (!isAwake) {
+            closeSwitcherIfOpenAndNotSimple(false);
+        }
+    }
+
+    private boolean isListAnimating() {
+        return mKeyguardVisibilityHelper.isVisibilityAnimating() || mListView.isAnimating();
+    }
+
+    /**
+     * Remove the callback if it exists.
+     */
+    public void removeCallback() {
+        if (DEBUG) Log.d(TAG, "removeCallback");
+        mKeyguardUserSwitcherCallback = null;
+    }
+
+    /**
+     * Register to receive notifications about keyguard user switcher state
+     * (see {@link KeyguardUserSwitcherListener}.
+     *
+     * Only one callback can be used at a time.
+     *
+     * @param callback The callback to register
+     */
+    public void setCallback(KeyguardUserSwitcherListener callback) {
+        if (DEBUG) Log.d(TAG, "setCallback");
+        mKeyguardUserSwitcherCallback = new WeakReference<>(callback);
+    }
+
+    /**
+     * If user switcher state changes, notifies all {@link KeyguardUserSwitcherListener}.
+     * Switcher state is updatd before animations finish.
+     *
+     * @param animate true to animate transition. The user switcher state (i.e.
+     *                {@link #isUserSwitcherOpen()}) is updated before animation is finished.
+     */
+    private void setUserSwitcherOpened(boolean open, boolean animate) {
+        boolean wasOpen = mUserSwitcherOpen;
+        if (DEBUG) {
+            Log.d(TAG, String.format("setUserSwitcherOpened: %b -> %b (animate=%b)", wasOpen,
+                    open, animate));
+        }
+        mUserSwitcherOpen = open;
+        if (mUserSwitcherOpen != wasOpen) {
+            notifyUserSwitcherStateChanged();
+        }
+        updateVisibilities(animate);
+    }
+
+    private void updateVisibilities(boolean animate) {
+        if (DEBUG) Log.d(TAG, String.format("updateVisibilities: animate=%b", animate));
+        mEndGuestButton.animate().cancel();
+        if (mUserSwitcherOpen && mCurrentUserIsGuest) {
+            // Show the "End guest session" button
+            mEndGuestButton.setVisibility(View.VISIBLE);
+            if (animate) {
+                mEndGuestButton.setAlpha(0f);
+                mEndGuestButton.animate()
+                        .alpha(1f)
+                        .setDuration(360)
+                        .setInterpolator(Interpolators.ALPHA_IN)
+                        .withEndAction(() -> {
+                            mEndGuestButton.setClickable(true);
+                        });
+            } else {
+                mEndGuestButton.setClickable(true);
+                mEndGuestButton.setAlpha(1f);
+            }
+        } else {
+            // Hide the "End guest session" button. If it's already GONE, don't try to
+            // animate it or it will appear again for an instant.
+            mEndGuestButton.setClickable(false);
+            if (animate && mEndGuestButton.getVisibility() != View.GONE) {
+                mEndGuestButton.setVisibility(View.VISIBLE);
+                mEndGuestButton.setAlpha(1f);
+                mEndGuestButton.animate()
+                        .alpha(0f)
+                        .setDuration(360)
+                        .setInterpolator(Interpolators.ALPHA_OUT)
+                        .withEndAction(() -> {
+                            mEndGuestButton.setVisibility(View.GONE);
+                            mEndGuestButton.setAlpha(1f);
+                        });
+            } else {
+                mEndGuestButton.setVisibility(View.GONE);
+                mEndGuestButton.setAlpha(1f);
+            }
+        }
+
+        mListView.updateVisibilities(mUserSwitcherOpen, animate);
+    }
+
+    private boolean isUserSwitcherOpen() {
+        return mUserSwitcherOpen;
+    }
+
+    private void notifyUserSwitcherStateChanged() {
+        if (DEBUG) {
+            Log.d(TAG, String.format("notifyUserSwitcherStateChanged: mUserSwitcherOpen=%b",
+                    mUserSwitcherOpen));
+        }
+        if (mKeyguardUserSwitcherCallback != null) {
+            KeyguardUserSwitcherListener cb = mKeyguardUserSwitcherCallback.get();
+            if (cb != null) {
+                cb.onKeyguardUserSwitcherChanged(mUserSwitcherOpen);
+            }
+        }
+    }
+
+    /**
+     * Callback for keyguard user switcher state information
+     */
+    public interface KeyguardUserSwitcherListener {
+
+        /**
+         * Called when the keyguard enters or leaves user switcher mode. This will be called
+         * before the animations are finished.
+         *
+         * @param open if true, keyguard is showing the user switcher or transitioning from/to user
+         *             switcher mode.
+         */
+        void onKeyguardUserSwitcherChanged(boolean open);
+    }
+
+    static class KeyguardUserAdapter extends
+            UserSwitcherController.BaseUserAdapter implements View.OnClickListener {
+
+        private final Context mContext;
+        private final Resources mResources;
+        private final LayoutInflater mLayoutInflater;
+        private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
+        private View mCurrentUserView;
+        // List of users where the first entry is always the current user
+        private ArrayList<UserSwitcherController.UserRecord> mUsersOrdered = new ArrayList<>();
+
+        KeyguardUserAdapter(Context context, Resources resources, LayoutInflater layoutInflater,
+                UserSwitcherController controller,
+                KeyguardUserSwitcherController keyguardUserSwitcherController) {
+            super(controller);
+            mContext = context;
+            mResources = resources;
+            mLayoutInflater = layoutInflater;
+            mKeyguardUserSwitcherController = keyguardUserSwitcherController;
+        }
+
+        @Override
+        public void notifyDataSetChanged() {
+            // At this point, value of isSimpleUserSwitcher() may have changed in addition to the
+            // data set
+            refreshUserOrder();
+            super.notifyDataSetChanged();
+        }
+
+        void refreshUserOrder() {
+            ArrayList<UserSwitcherController.UserRecord> users = super.getUsers();
+            mUsersOrdered = new ArrayList<>(users.size());
+            for (int i = 0; i < users.size(); i++) {
+                UserSwitcherController.UserRecord record = users.get(i);
+                if (record.isCurrent) {
+                    mUsersOrdered.add(0, record);
+                } else {
+                    mUsersOrdered.add(record);
+                }
+            }
+        }
+
+        @Override
+        protected ArrayList<UserSwitcherController.UserRecord> getUsers() {
+            return mUsersOrdered;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            UserSwitcherController.UserRecord item = getItem(position);
+            return createUserDetailItemView(convertView, parent, item);
+        }
+
+        @Override
+        public String getName(Context context, UserSwitcherController.UserRecord item) {
+            if (item.isGuest) {
+                return context.getString(com.android.settingslib.R.string.guest_nickname);
+            } else {
+                return super.getName(context, item);
+            }
+        }
+
+        KeyguardUserDetailItemView convertOrInflate(View convertView, ViewGroup parent) {
+            if (!(convertView instanceof KeyguardUserDetailItemView)
+                    || !(convertView.getTag() instanceof UserSwitcherController.UserRecord)) {
+                convertView = mLayoutInflater.inflate(
+                        R.layout.keyguard_user_switcher_item, parent, false);
+            }
+            return (KeyguardUserDetailItemView) convertView;
+        }
+
+        KeyguardUserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
+                UserSwitcherController.UserRecord item) {
+            KeyguardUserDetailItemView v = convertOrInflate(convertView, parent);
+            v.setOnClickListener(this);
+
+            String name = getName(mContext, item);
+            if (item.picture == null) {
+                v.bind(name, getDrawable(item).mutate(), item.resolveId());
+            } else {
+                int avatarSize =
+                        (int) mResources.getDimension(R.dimen.kg_framed_avatar_size);
+                Drawable drawable = new CircleFramedDrawable(item.picture, avatarSize);
+                drawable.setColorFilter(
+                        item.isSwitchToEnabled ? null : getDisabledUserAvatarColorFilter());
+                v.bind(name, drawable, item.info.id);
+            }
+            v.setActivated(item.isCurrent);
+            v.setDisabledByAdmin(item.isDisabledByAdmin);
+            v.setEnabled(item.isSwitchToEnabled);
+            v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA);
+
+            if (item.isCurrent) {
+                mCurrentUserView = v;
+            }
+            v.setTag(item);
+            return v;
+        }
+
+        private Drawable getDrawable(UserSwitcherController.UserRecord item) {
+            Drawable drawable;
+            if (item.isCurrent && item.isGuest) {
+                drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user);
+            } else {
+                drawable = getIconDrawable(mContext, item);
+            }
+
+            int iconColorRes;
+            if (item.isSwitchToEnabled) {
+                iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
+            } else {
+                iconColorRes = R.color.kg_user_switcher_restricted_avatar_icon_color;
+            }
+            drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme()));
+
+            Drawable bg = mContext.getDrawable(R.drawable.kg_bg_avatar);
+            drawable = new LayerDrawable(new Drawable[]{bg, drawable});
+            return drawable;
+        }
+
+        @Override
+        public void onClick(View v) {
+            UserSwitcherController.UserRecord user = (UserSwitcherController.UserRecord) v.getTag();
+
+            if (mKeyguardUserSwitcherController.isListAnimating()) {
+                return;
+            }
+
+            if (mKeyguardUserSwitcherController.isUserSwitcherOpen()) {
+                if (user.isCurrent) {
+                    // Close the switcher if tapping the current user
+                    mKeyguardUserSwitcherController.setUserSwitcherOpened(
+                            false /* open */, true /* animate */);
+                } else if (user.isSwitchToEnabled) {
+                    if (!user.isAddUser && !user.isRestricted && !user.isDisabledByAdmin) {
+                        if (mCurrentUserView != null) {
+                            mCurrentUserView.setActivated(false);
+                        }
+                        v.setActivated(true);
+                    }
+                    onUserListItemClicked(user);
+                }
+            } else {
+                // If switcher is closed, tapping anywhere in the view will open it
+                mKeyguardUserSwitcherController.setUserSwitcherOpened(
+                        true /* open */, true /* animate */);
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
new file mode 100644
index 0000000..7c82c11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+import com.android.keyguard.AlphaOptimizedLinearLayout;
+import com.android.keyguard.KeyguardConstants;
+import com.android.settingslib.animation.AppearAnimationUtils;
+import com.android.settingslib.animation.DisappearAnimationUtils;
+import com.android.systemui.Interpolators;
+
+/**
+ * The container for the user switcher on Keyguard.
+ */
+public class KeyguardUserSwitcherListView extends AlphaOptimizedLinearLayout {
+
+    private static final String TAG = "KeyguardUserSwitcherListView";
+    private static final boolean DEBUG = KeyguardConstants.DEBUG;
+
+    private static final int ANIMATION_DURATION_OPENING = 360;
+    private static final int ANIMATION_DURATION_CLOSING = 240;
+
+    private boolean mAnimating;
+    private final AppearAnimationUtils mAppearAnimationUtils;
+    private final DisappearAnimationUtils mDisappearAnimationUtils;
+
+    public KeyguardUserSwitcherListView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setClipChildren(false);
+        mAppearAnimationUtils = new AppearAnimationUtils(context, ANIMATION_DURATION_OPENING,
+                -0.5f, 0.5f, Interpolators.FAST_OUT_SLOW_IN);
+        mDisappearAnimationUtils = new DisappearAnimationUtils(context, ANIMATION_DURATION_CLOSING,
+                0.5f, 0.5f, Interpolators.FAST_OUT_LINEAR_IN);
+    }
+
+    /**
+     * Set the amount (ratio) that the device has transitioned to doze.
+     *
+     * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
+     */
+    void setDarkAmount(float darkAmount) {
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View v = getChildAt(i);
+            if (v instanceof KeyguardUserDetailItemView) {
+                ((KeyguardUserDetailItemView) v).setDarkAmount(darkAmount);
+            }
+        }
+    }
+
+    boolean isAnimating() {
+        return mAnimating;
+    }
+
+    /**
+     * Update visibilities of this view and child views for when the user list is open or closed.
+     * If closed, this hides everything but the first item (which is always the current user).
+     */
+    void updateVisibilities(boolean open, boolean animate) {
+        if (DEBUG) {
+            Log.d(TAG, String.format("updateVisibilities: open=%b animate=%b childCount=%d",
+                    open, animate, getChildCount()));
+        }
+
+        mAnimating = false;
+
+        int userListCount = getChildCount();
+        if (userListCount > 0) {
+            // The first child is always the current user.
+            KeyguardUserDetailItemView currentUserView = ((KeyguardUserDetailItemView) getChildAt(
+                    0));
+            currentUserView.updateVisibilities(true /* showItem */, open /* showTextName */,
+                    animate);
+            currentUserView.setClickable(true);
+            currentUserView.clearAnimation();
+        }
+
+        if (userListCount <= 1) {
+            return;
+        }
+
+        if (animate) {
+            // Create an array of all the remaining users (that aren't the current user).
+            KeyguardUserDetailItemView[] otherUserViews =
+                    new KeyguardUserDetailItemView[userListCount - 1];
+            for (int i = 1, n = 0; i < userListCount; i++, n++) {
+                otherUserViews[n] = (KeyguardUserDetailItemView) getChildAt(i);
+
+                // Update clickable state immediately so that the menu feels more responsive
+                otherUserViews[n].setClickable(open);
+
+                // Before running the animation, ensure visibility is set correctly
+                otherUserViews[n].updateVisibilities(
+                        true /* showItem */, true /* showTextName */, false /* animate */);
+                otherUserViews[n].clearAnimation();
+            }
+
+            setClipChildren(false);
+            setClipToPadding(false);
+
+            mAnimating = true;
+
+            final int nonCurrentUserCount = otherUserViews.length;
+            if (open) {
+                mAppearAnimationUtils.startAnimation(otherUserViews, () -> {
+                    setClipChildren(true);
+                    setClipToPadding(true);
+                    mAnimating = false;
+                });
+            } else {
+                mDisappearAnimationUtils.startAnimation(otherUserViews, () -> {
+                    setClipChildren(true);
+                    setClipToPadding(true);
+                    for (int i = 0; i < nonCurrentUserCount; i++) {
+                        otherUserViews[i].updateVisibilities(
+                                false /* showItem */, true /* showTextName */, false /* animate */);
+                    }
+                    mAnimating = false;
+                });
+            }
+        } else {
+            for (int i = 1; i < userListCount; i++) {
+                KeyguardUserDetailItemView nonCurrentUserView =
+                        ((KeyguardUserDetailItemView) getChildAt(i));
+                nonCurrentUserView.clearAnimation();
+                nonCurrentUserView.updateVisibilities(
+                        open /* showItem */, true /* showTextName */, false /* animate */);
+                nonCurrentUserView.setClickable(open);
+            }
+        }
+    }
+
+    /**
+     * Replaces the view at the specified position in the group.
+     *
+     * @param index the position in the group of the view to remove
+     */
+    void replaceView(KeyguardUserDetailItemView newView, int index) {
+        removeViewAt(index);
+        addView(newView, index);
+    }
+
+    /**
+     * Removes the last view in the group.
+     */
+    void removeLastView() {
+        int lastIndex = getChildCount() - 1;
+        removeViewAt(lastIndex);
+    }
+}
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherView.java
similarity index 61%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherView.java
index 14d57bf..3f0e23f 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherView.java
@@ -14,6 +14,18 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package com.android.systemui.statusbar.policy;
 
-parcelable ExternalTimeSuggestion;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+/**
+ * The container for the user switcher on Keyguard.
+ */
+public class KeyguardUserSwitcherView extends FrameLayout {
+
+    public KeyguardUserSwitcherView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 68d74ef..1838391 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -423,7 +423,7 @@
         }
     }
 
-    private void showExitGuestDialog(int id) {
+    protected void showExitGuestDialog(int id) {
         int newId = UserHandle.USER_SYSTEM;
         if (mResumeUserOnGuestLogout && mLastNonGuestUser != UserHandle.USER_SYSTEM) {
             UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index e357577..3f01414 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -15,12 +15,15 @@
  */
 package com.android.systemui.theme;
 
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayManager;
-import android.os.SystemProperties;
+import android.content.om.OverlayManagerTransaction;
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.Pair;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
@@ -50,13 +53,6 @@
 public class ThemeOverlayApplier implements Dumpable {
     private static final String TAG = "ThemeOverlayApplier";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    private static final boolean MONET_ENABLED = SystemProperties
-            .getBoolean("persist.sysui.monet", false);
-
-    @VisibleForTesting
-    static final String MONET_ACCENT_COLOR_PACKAGE = "com.android.theme.accentcolor.color";
-    @VisibleForTesting
-    static final String MONET_SYSTEM_PALETTE_PACKAGE = "com.android.theme.systemcolors.color";
 
     @VisibleForTesting
     static final String ANDROID_PACKAGE = "android";
@@ -65,10 +61,8 @@
     @VisibleForTesting
     static final String SYSUI_PACKAGE = "com.android.systemui";
 
-    @VisibleForTesting
     static final String OVERLAY_CATEGORY_ACCENT_COLOR =
             "android.theme.customization.accent_color";
-    @VisibleForTesting
     static final String OVERLAY_CATEGORY_SYSTEM_PALETTE =
             "android.theme.customization.system_palette";
     @VisibleForTesting
@@ -117,16 +111,6 @@
             OVERLAY_CATEGORY_ICON_ANDROID,
             OVERLAY_CATEGORY_ICON_SYSUI);
 
-    /**
-     * List of main colors of Monet themes. These are extracted from overlays installed
-     * on the system.
-     */
-    private final ArrayList<Integer> mMainSystemColors = new ArrayList<>();
-    /**
-     * Same as above, but providing accent colors instead of a system palette.
-     */
-    private final ArrayList<Integer> mAccentColors = new ArrayList<>();
-
     /* Allowed overlay categories for each target package. */
     private final Map<String, Set<String>> mTargetPackageToCategories = new ArrayMap<>();
     /* Target package for each overlay category. */
@@ -162,64 +146,17 @@
         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_LAUNCHER, mLauncherPackage);
         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_THEME_PICKER, mThemePickerPackage);
 
-        collectMonetSystemOverlays();
         dumpManager.registerDumpable(TAG, this);
     }
 
     /**
-     * List of accent colors available as Monet overlays.
-     */
-    List<Integer> getAvailableAccentColors() {
-        return mAccentColors;
-    }
-
-    /**
-     * List of main system colors available as Monet overlays.
-     */
-    List<Integer> getAvailableSystemColors() {
-        return mMainSystemColors;
-    }
-
-    private void collectMonetSystemOverlays() {
-        if (!MONET_ENABLED) {
-            return;
-        }
-        List<OverlayInfo> androidOverlays = mOverlayManager
-                .getOverlayInfosForTarget(ANDROID_PACKAGE, UserHandle.SYSTEM);
-        for (OverlayInfo overlayInfo : androidOverlays) {
-            String packageName = overlayInfo.packageName;
-            if (DEBUG) {
-                Log.d(TAG, "Processing overlay " + packageName);
-            }
-            if (OVERLAY_CATEGORY_SYSTEM_PALETTE.equals(overlayInfo.category)
-                    && packageName.startsWith(MONET_SYSTEM_PALETTE_PACKAGE)) {
-                try {
-                    String color = packageName.replace(MONET_SYSTEM_PALETTE_PACKAGE, "");
-                    mMainSystemColors.add(Integer.parseInt(color, 16));
-                } catch (NumberFormatException e) {
-                    Log.w(TAG, "Invalid package name for overlay " + packageName, e);
-                }
-            } else if (OVERLAY_CATEGORY_ACCENT_COLOR.equals(overlayInfo.category)
-                    && packageName.startsWith(MONET_ACCENT_COLOR_PACKAGE)) {
-                try {
-                    String color = packageName.replace(MONET_ACCENT_COLOR_PACKAGE, "");
-                    mAccentColors.add(Integer.parseInt(color, 16));
-                } catch (NumberFormatException e) {
-                    Log.w(TAG, "Invalid package name for overlay " + packageName, e);
-                }
-            } else if (DEBUG) {
-                Log.d(TAG, "Unknown overlay: " + packageName + " category: "
-                        + overlayInfo.category);
-            }
-        }
-    }
-
-    /**
      * Apply the set of overlay packages to the set of {@code UserHandle}s provided. Overlays that
      * affect sysui will also be applied to the system user.
      */
     void applyCurrentUserOverlays(
-            Map<String, String> categoryToPackage, Set<UserHandle> userHandles) {
+            Map<String, OverlayIdentifier> categoryToPackage,
+            FabricatedOverlay[] pendingCreation,
+            Set<UserHandle> userHandles) {
         // Disable all overlays that have not been specified in the user setting.
         final Set<String> overlayCategoriesToDisable = new HashSet<>(THEME_CATEGORIES);
         overlayCategoriesToDisable.removeAll(categoryToPackage.keySet());
@@ -229,55 +166,64 @@
         final List<OverlayInfo> overlays = new ArrayList<>();
         targetPackagesToQuery.forEach(targetPackage -> overlays.addAll(mOverlayManager
                 .getOverlayInfosForTarget(targetPackage, UserHandle.SYSTEM)));
-        final Map<String, String> overlaysToDisable = overlays.stream()
+        final List<Pair<String, String>> overlaysToDisable = overlays.stream()
                 .filter(o ->
                         mTargetPackageToCategories.get(o.targetPackageName).contains(o.category))
                 .filter(o -> overlayCategoriesToDisable.contains(o.category))
                 .filter(o -> o.isEnabled())
-                .collect(Collectors.toMap((o) -> o.category, (o) -> o.packageName));
+                .map(o -> new Pair<>(o.category, o.packageName))
+                .collect(Collectors.toList());
+
+        OverlayManagerTransaction.Builder transaction = getTransactionBuilder();
+        if (pendingCreation != null) {
+            for (FabricatedOverlay overlay : pendingCreation) {
+                transaction.registerFabricatedOverlay(overlay);
+            }
+        }
 
         // Toggle overlays in the order of THEME_CATEGORIES.
         for (String category : THEME_CATEGORIES) {
             if (categoryToPackage.containsKey(category)) {
-                setEnabled(categoryToPackage.get(category), category, userHandles, true);
-            } else if (overlaysToDisable.containsKey(category)) {
-                setEnabled(overlaysToDisable.get(category), category, userHandles, false);
+                OverlayIdentifier overlayInfo = categoryToPackage.get(category);
+                setEnabled(transaction, overlayInfo, category, userHandles, true);
             }
         }
+        for (Pair<String, String> packageToDisable : overlaysToDisable) {
+            OverlayIdentifier overlayInfo = new OverlayIdentifier(packageToDisable.second);
+            setEnabled(transaction, overlayInfo, packageToDisable.first, userHandles, false);
+        }
+
+        mExecutor.execute(() -> {
+            mOverlayManager.commit(transaction.build());
+        });
     }
 
-    private void setEnabled(
-            String packageName, String category, Set<UserHandle> handles, boolean enabled) {
+    @VisibleForTesting
+    protected OverlayManagerTransaction.Builder getTransactionBuilder() {
+        return new OverlayManagerTransaction.Builder();
+    }
+
+    private void setEnabled(OverlayManagerTransaction.Builder transaction,
+            OverlayIdentifier identifier, String category, Set<UserHandle> handles,
+            boolean enabled) {
+        if (DEBUG) {
+            Log.d(TAG, "setEnabled: " + identifier.getPackageName() + " category: "
+                    + category + ": " + enabled);
+        }
         for (UserHandle userHandle : handles) {
-            setEnabledAsync(packageName, userHandle, enabled);
+            transaction.setEnabled(identifier, enabled, userHandle.getIdentifier());
         }
         if (!handles.contains(UserHandle.SYSTEM) && SYSTEM_USER_CATEGORIES.contains(category)) {
-            setEnabledAsync(packageName, UserHandle.SYSTEM, enabled);
+            transaction.setEnabled(identifier, enabled, UserHandle.SYSTEM.getIdentifier());
         }
     }
 
-    private void setEnabledAsync(String pkg, UserHandle userHandle, boolean enabled) {
-        mExecutor.execute(() -> {
-            if (DEBUG) Log.d(TAG, String.format("setEnabled: %s %s %b", pkg, userHandle, enabled));
-            try {
-                if (enabled) {
-                    mOverlayManager.setEnabledExclusiveInCategory(pkg, userHandle);
-                } else {
-                    mOverlayManager.setEnabled(pkg, false, userHandle);
-                }
-            } catch (SecurityException | IllegalStateException e) {
-                Log.e(TAG,
-                        String.format("setEnabled failed: %s %s %b", pkg, userHandle, enabled), e);
-            }
-        });
-    }
-
     /**
      * @inherit
      */
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
-        pw.println("mMainSystemColors=" + mMainSystemColors.size());
-        pw.println("mAccentColors=" + mAccentColors.size());
+        pw.println("mTargetPackageToCategories=" + mTargetPackageToCategories);
+        pw.println("mCategoryToTargetPackage=" + mCategoryToTargetPackage);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index d9f4744..522a42b 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -18,6 +18,7 @@
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.WallpaperColors;
 import android.app.WallpaperManager;
@@ -25,6 +26,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayIdentifier;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.graphics.Color;
@@ -40,7 +43,6 @@
 import androidx.annotation.NonNull;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.ColorUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.SystemUI;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -48,6 +50,7 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -59,10 +62,10 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Collection;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 
@@ -77,9 +80,12 @@
  */
 @SysUISingleton
 public class ThemeOverlayController extends SystemUI implements Dumpable {
-    private static final String TAG = "ThemeOverlayController";
+    protected static final String TAG = "ThemeOverlayController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
+    protected static final int MAIN = 0;
+    protected static final int ACCENT = 1;
+
     // If lock screen wallpaper colors should also be considered when selecting the theme.
     // Doing this has performance impact, given that overlays would need to be swapped when
     // the device unlocks.
@@ -95,16 +101,19 @@
     private final Handler mBgHandler;
     private final WallpaperManager mWallpaperManager;
     private final KeyguardStateController mKeyguardStateController;
+    private final boolean mIsMonetEnabled;
     private WallpaperColors mLockColors;
     private WallpaperColors mSystemColors;
-    // Color extracted from wallpaper, NOT the color used on the overlay
+    // If fabricated overlays were already created for the current theme.
+    private boolean mNeedsOverlayCreation;
+    // Dominant olor extracted from wallpaper, NOT the color used on the overlay
     protected int mMainWallpaperColor = Color.TRANSPARENT;
-    // Color extracted from wallpaper, NOT the color used on the overlay
+    // Accent color extracted from wallpaper, NOT the color used on the overlay
     protected int mWallpaperAccentColor = Color.TRANSPARENT;
-    // Main system color that maps to an overlay color
-    private int mSystemOverlayColor = Color.TRANSPARENT;
-    // Accent color that maps to an overlay color
-    private int mAccentOverlayColor = Color.TRANSPARENT;
+    // System colors overlay
+    private FabricatedOverlay mSystemOverlay;
+    // Accent colors overlay
+    private FabricatedOverlay mAccentOverlay;
 
     @Inject
     public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher,
@@ -112,9 +121,10 @@
             @Background Executor bgExecutor, ThemeOverlayApplier themeOverlayApplier,
             SecureSettings secureSettings, WallpaperManager wallpaperManager,
             UserManager userManager, KeyguardStateController keyguardStateController,
-            DumpManager dumpManager) {
+            DumpManager dumpManager, FeatureFlags featureFlags) {
         super(context);
 
+        mIsMonetEnabled = featureFlags.isMonetEnabled();
         mBroadcastDispatcher = broadcastDispatcher;
         mUserManager = userManager;
         mBgExecutor = bgExecutor;
@@ -221,20 +231,16 @@
         mMainWallpaperColor = mainColor;
         mWallpaperAccentColor = accentCandidate;
 
-        // Let's compare these colors to our finite set of overlays, and then pick an overlay.
-        List<Integer> systemColors = mThemeManager.getAvailableSystemColors();
-        List<Integer> accentColors = mThemeManager.getAvailableAccentColors();
-
-        if (systemColors.size() == 0 || accentColors.size() == 0) {
+        if (mIsMonetEnabled) {
+            mSystemOverlay = getOverlay(mMainWallpaperColor, MAIN);
+            mAccentOverlay = getOverlay(mWallpaperAccentColor, ACCENT);
+            mNeedsOverlayCreation = true;
             if (DEBUG) {
-                Log.d(TAG, "Cannot apply system theme, palettes are unavailable");
+                Log.d(TAG, "fetched overlays. system: " + mSystemOverlay + " accent: "
+                        + mAccentOverlay);
             }
-            return;
         }
 
-        mSystemOverlayColor = getClosest(systemColors, mMainWallpaperColor);
-        mAccentOverlayColor = getClosest(accentColors, mWallpaperAccentColor);
-
         updateThemeOverlays();
     }
 
@@ -257,42 +263,10 @@
     }
 
     /**
-     * Given a color and a list of candidates, return the candidate that's the most similar to the
-     * given color.
+     * Given a color candidate, return an overlay definition.
      */
-    protected int getClosest(List<Integer> candidates, int color) {
-        float[] hslMain = new float[3];
-        float[] hslCandidate = new float[3];
-
-        ColorUtils.RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hslMain);
-        hslMain[0] /= 360f;
-
-        // To close to white or black, let's use the default system theme instead of
-        // applying a colorized one.
-        if (hslMain[2] < 0.05 || hslMain[2] > 0.95) {
-            return Color.TRANSPARENT;
-        }
-
-        float minDistance = Float.MAX_VALUE;
-        int closestColor = Color.TRANSPARENT;
-        for (int candidate: candidates) {
-            ColorUtils.RGBToHSL(Color.red(candidate), Color.green(candidate), Color.blue(candidate),
-                    hslCandidate);
-            hslCandidate[0] /= 360f;
-
-            float sqDistance = squared(hslCandidate[0] - hslMain[0])
-                    + squared(hslCandidate[1] - hslMain[1])
-                    + squared(hslCandidate[2] - hslMain[2]);
-            if (sqDistance < minDistance) {
-                minDistance = sqDistance;
-                closestColor = candidate;
-            }
-        }
-        return closestColor;
-    }
-
-    private static float squared(float f) {
-        return f * f;
+    protected @Nullable FabricatedOverlay getOverlay(int color, int type) {
+        return null;
     }
 
     private void updateThemeOverlays() {
@@ -301,20 +275,15 @@
                 Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
                 currentUser);
         if (DEBUG) Log.d(TAG, "updateThemeOverlays. Setting: " + overlayPackageJson);
-        boolean hasSystemPalette = false;
-        boolean hasAccentColor = false;
-        final Map<String, String> categoryToPackage = new ArrayMap<>();
+        final Map<String, OverlayIdentifier> categoryToPackage = new ArrayMap<>();
         if (!TextUtils.isEmpty(overlayPackageJson)) {
             try {
                 JSONObject object = new JSONObject(overlayPackageJson);
                 for (String category : ThemeOverlayApplier.THEME_CATEGORIES) {
                     if (object.has(category)) {
-                        if (category.equals(OVERLAY_CATEGORY_ACCENT_COLOR)) {
-                            hasAccentColor = true;
-                        } else if (category.equals(OVERLAY_CATEGORY_SYSTEM_PALETTE)) {
-                            hasSystemPalette = true;
-                        }
-                        categoryToPackage.put(category, object.getString(category));
+                        OverlayIdentifier identifier =
+                                new OverlayIdentifier(object.getString(category));
+                        categoryToPackage.put(category, identifier);
                     }
                 }
             } catch (JSONException e) {
@@ -322,17 +291,41 @@
             }
         }
 
-        // Let's apply the system palette, but only if it was not overridden by the style picker.
-        if (!hasSystemPalette && mSystemOverlayColor != Color.TRANSPARENT) {
-            categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE,
-                    ThemeOverlayApplier.MONET_SYSTEM_PALETTE_PACKAGE
-                            + getColorString(mSystemOverlayColor));
+        // Let's generate system overlay if the style picker decided to override it.
+        OverlayIdentifier systemPalette = categoryToPackage.get(OVERLAY_CATEGORY_SYSTEM_PALETTE);
+        if (mIsMonetEnabled && systemPalette != null && systemPalette.getPackageName() != null) {
+            try {
+                int color = Integer.parseInt(systemPalette.getPackageName().toLowerCase(), 16);
+                mSystemOverlay = getOverlay(color, MAIN);
+                mNeedsOverlayCreation = true;
+                categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE);
+            } catch (NumberFormatException e) {
+                Log.w(TAG, "Invalid color definition: " + systemPalette.getPackageName());
+            }
         }
-        // Same for the accent color
-        if (!hasAccentColor && mAccentOverlayColor != Color.TRANSPARENT) {
-            categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR,
-                    ThemeOverlayApplier.MONET_ACCENT_COLOR_PACKAGE
-                            + getColorString(mAccentOverlayColor));
+
+        // Same for accent color.
+        OverlayIdentifier accentPalette = categoryToPackage.get(OVERLAY_CATEGORY_ACCENT_COLOR);
+        if (mIsMonetEnabled && accentPalette != null && accentPalette.getPackageName() != null) {
+            try {
+                int color = Integer.parseInt(accentPalette.getPackageName().toLowerCase(), 16);
+                mAccentOverlay = getOverlay(color, ACCENT);
+                mNeedsOverlayCreation = true;
+                categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR);
+            } catch (NumberFormatException e) {
+                Log.w(TAG, "Invalid color definition: " + accentPalette.getPackageName());
+            }
+        }
+
+        // Compatibility with legacy themes, where full packages were defined, instead of just
+        // colors.
+        if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE)
+                && mSystemOverlay != null) {
+            categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE, mSystemOverlay.getIdentifier());
+        }
+        if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_ACCENT_COLOR)
+                && mAccentOverlay != null) {
+            categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, mAccentOverlay.getIdentifier());
         }
 
         Set<UserHandle> userHandles = Sets.newHashSet(UserHandle.of(currentUser));
@@ -341,28 +334,31 @@
                 userHandles.add(userInfo.getUserHandle());
             }
         }
-        mThemeManager.applyCurrentUserOverlays(categoryToPackage, userHandles);
-    }
-
-    private String getColorString(int color) {
-        String colorString = Integer.toHexString(color).toUpperCase();
-        while (colorString.length() < 6) {
-            colorString = "0" + colorString;
+        if (DEBUG) {
+            Log.d(TAG, "Applying overlays: " + categoryToPackage.keySet().stream()
+                    .map(key -> key + " -> " + categoryToPackage.get(key)).collect(
+                            Collectors.joining(", ")));
         }
-        // Remove alpha component
-        if (colorString.length() > 6) {
-            colorString = colorString.substring(colorString.length() - 6);
+        if (mNeedsOverlayCreation) {
+            mNeedsOverlayCreation = false;
+            mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] {
+                    mSystemOverlay, mAccentOverlay
+            }, userHandles);
+        } else {
+            mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, userHandles);
         }
-        return colorString;
     }
 
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.println("USE_LOCK_SCREEN_WALLPAPER=" + USE_LOCK_SCREEN_WALLPAPER);
         pw.println("mLockColors=" + mLockColors);
         pw.println("mSystemColors=" + mSystemColors);
         pw.println("mMainWallpaperColor=" + Integer.toHexString(mMainWallpaperColor));
         pw.println("mWallpaperAccentColor=" + Integer.toHexString(mWallpaperAccentColor));
-        pw.println("mSystemOverlayColor=" + Integer.toHexString(mSystemOverlayColor));
-        pw.println("mAccentOverlayColor=" + Integer.toHexString(mAccentOverlayColor));
+        pw.println("mSystemOverlayColor=" + mSystemOverlay);
+        pw.println("mAccentOverlayColor=" + mAccentOverlay);
+        pw.println("mIsMonetEnabled=" + mIsMonetEnabled);
+        pw.println("mNeedsOverlayCreation=" + mNeedsOverlayCreation);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
index e9fcf1a..365cd2a 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
@@ -20,10 +20,21 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
 import android.widget.ToastPresenter;
 
 import com.android.internal.R;
+import com.android.launcher3.icons.IconFactory;
 import com.android.systemui.plugins.ToastPlugin;
 
 /**
@@ -35,23 +46,43 @@
     final CharSequence mText;
     final ToastPlugin.Toast mPluginToast;
 
-    final int mDefaultGravity;
-    final int mDefaultY;
+    private final String mPackageName;
+    private final int mUserId;
+    private final LayoutInflater mLayoutInflater;
+    private final boolean mToastStyleEnabled;
+
     final int mDefaultX = 0;
     final int mDefaultHorizontalMargin = 0;
     final int mDefaultVerticalMargin = 0;
 
-    SystemUIToast(Context context, CharSequence text) {
-        this(context, text, null);
+    private int mDefaultY;
+    private int mDefaultGravity;
+
+    @NonNull private final View mToastView;
+    @Nullable private final Animator mInAnimator;
+    @Nullable private final Animator mOutAnimator;
+
+    SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text,
+            String packageName, int userId, boolean toastStyleEnabled, int orientation) {
+        this(layoutInflater, context, text, null, packageName, userId,
+                toastStyleEnabled, orientation);
     }
 
-    SystemUIToast(Context context, CharSequence text, ToastPlugin.Toast pluginToast) {
+    SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text,
+            ToastPlugin.Toast pluginToast, String packageName, int userId,
+            boolean toastStyleEnabled, int orientation) {
+        mToastStyleEnabled = toastStyleEnabled;
+        mLayoutInflater = layoutInflater;
         mContext = context;
         mText = text;
         mPluginToast = pluginToast;
+        mPackageName = packageName;
+        mUserId = userId;
+        mToastView = inflateToastView();
+        mInAnimator = createInAnimator();
+        mOutAnimator = createOutAnimator();
 
-        mDefaultGravity = context.getResources().getInteger(R.integer.config_toastDefaultGravity);
-        mDefaultY = context.getResources().getDimensionPixelSize(R.dimen.toast_y_offset);
+        onOrientationChange(orientation);
     }
 
     @Override
@@ -102,28 +133,19 @@
     @Override
     @NonNull
     public View getView() {
-        if (isPluginToast() && mPluginToast.getView() != null) {
-            return mPluginToast.getView();
-        }
-        return ToastPresenter.getTextToastView(mContext, mText);
+        return mToastView;
     }
 
     @Override
     @Nullable
     public Animator getInAnimation() {
-        if (isPluginToast() && mPluginToast.getInAnimation() != null) {
-            return mPluginToast.getInAnimation();
-        }
-        return null;
+        return mInAnimator;
     }
 
     @Override
     @Nullable
     public Animator getOutAnimation() {
-        if (isPluginToast() && mPluginToast.getOutAnimation() != null) {
-            return mPluginToast.getOutAnimation();
-        }
-        return null;
+        return mOutAnimator;
     }
 
     /**
@@ -136,4 +158,80 @@
     private boolean isPluginToast() {
         return mPluginToast != null;
     }
+
+    private View inflateToastView() {
+        if (isPluginToast() && mPluginToast.getView() != null) {
+            return mPluginToast.getView();
+        }
+
+        View toastView;
+        if (mToastStyleEnabled) {
+            toastView = mLayoutInflater.inflate(
+                    com.android.systemui.R.layout.text_toast, null);
+            ((TextView) toastView.findViewById(com.android.systemui.R.id.text)).setText(mText);
+
+            ((ImageView) toastView.findViewById(com.android.systemui.R.id.icon))
+                    .setImageDrawable(getBadgedIcon(mContext, mPackageName, mUserId));
+        } else {
+            toastView = ToastPresenter.getTextToastView(mContext, mText);
+        }
+
+        return toastView;
+    }
+
+    /**
+     * Called on orientation changes to update parameters associated with the toast placement.
+     */
+    public void onOrientationChange(int orientation) {
+        if (mPluginToast != null) {
+            mPluginToast.onOrientationChange(orientation);
+        }
+
+        mDefaultY = mContext.getResources().getDimensionPixelSize(
+                mToastStyleEnabled
+                        ? com.android.systemui.R.dimen.toast_y_offset
+                        : R.dimen.toast_y_offset);
+        mDefaultGravity =
+                mContext.getResources().getInteger(R.integer.config_toastDefaultGravity);
+    }
+
+    private Animator createInAnimator() {
+        if (isPluginToast() && mPluginToast.getInAnimation() != null) {
+            return mPluginToast.getInAnimation();
+        }
+
+        return mToastStyleEnabled
+                ? ToastDefaultAnimation.Companion.toastIn(getView())
+                : null;
+    }
+
+    private Animator createOutAnimator() {
+        if (isPluginToast() && mPluginToast.getOutAnimation() != null) {
+            return mPluginToast.getOutAnimation();
+        }
+        return mToastStyleEnabled
+                ? ToastDefaultAnimation.Companion.toastOut(getView())
+                : null;
+    }
+
+    /**
+     * Get badged app icon if necessary, similar as used in the Settings UI.
+     * @return The icon to use
+     */
+    public static Drawable getBadgedIcon(@NonNull Context context, String packageName,
+            int userId) {
+        final PackageManager packageManager = context.getPackageManager();
+        try {
+            final ApplicationInfo appInfo = packageManager.getApplicationInfoAsUser(
+                    packageName, PackageManager.GET_META_DATA, userId);
+            UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid);
+            IconFactory iconFactory = IconFactory.obtain(context);
+            Bitmap iconBmp = iconFactory.createBadgedIconBitmap(
+                    appInfo.loadUnbadgedIcon(packageManager), user, false).icon;
+            return new BitmapDrawable(context.getResources(), iconBmp);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e("SystemUIToast", "could not load icon for package=" + packageName + " e=" + e);
+            return null;
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt
new file mode 100644
index 0000000..603d690
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.toast
+
+import android.animation.ObjectAnimator
+import android.view.View
+import android.view.animation.LinearInterpolator
+import android.view.animation.PathInterpolator
+import android.animation.AnimatorSet
+
+class ToastDefaultAnimation {
+    /**
+     * sum of the in and out animation durations cannot exceed
+     * [com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_ANIM_BUFFER] to prevent the toast
+     * window from being removed before animations are completed
+     */
+    companion object {
+        // total duration shouldn't exceed NotificationManagerService's delay for "in" animation
+        fun toastIn(view: View): AnimatorSet? {
+            val icon: View? = view.findViewById(com.android.systemui.R.id.icon)
+            val text: View? = view.findViewById(com.android.systemui.R.id.text)
+            if (icon == null || text == null) {
+                return null
+            }
+            val linearInterp = LinearInterpolator()
+            val scaleInterp = PathInterpolator(0f, 0f, 0f, 1f)
+            val sX = ObjectAnimator.ofFloat(view, "scaleX", 0.9f, 1f).apply {
+                interpolator = scaleInterp
+                duration = 333
+            }
+            val sY = ObjectAnimator.ofFloat(view, "scaleY", 0.9f, 1f).apply {
+                interpolator = scaleInterp
+                duration = 333
+            }
+            val vA = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f).apply {
+                interpolator = linearInterp
+                duration = 66
+            }
+            text.alpha = 0f // Set now otherwise won't apply until start delay
+            val tA = ObjectAnimator.ofFloat(text, "alpha", 0f, 1f).apply {
+                interpolator = linearInterp
+                duration = 283
+                startDelay = 50
+            }
+            icon.alpha = 0f // Set now otherwise won't apply until start delay
+            val iA = ObjectAnimator.ofFloat(icon, "alpha", 0f, 1f).apply {
+                interpolator = linearInterp
+                duration = 283
+                startDelay = 50
+            }
+            return AnimatorSet().apply {
+                playTogether(sX, sY, vA, tA, iA)
+            }
+        }
+
+        fun toastOut(view: View): AnimatorSet? {
+            // total duration shouldn't exceed NotificationManagerService's delay for "out" anim
+            val icon: View? = view.findViewById(com.android.systemui.R.id.icon)
+            val text: View? = view.findViewById(com.android.systemui.R.id.text)
+            if (icon == null || text == null) {
+                return null
+            }
+            val linearInterp = LinearInterpolator()
+            val scaleInterp = PathInterpolator(0.3f, 0f, 1f, 1f)
+            val sX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.9f).apply {
+                interpolator = scaleInterp
+                duration = 250
+            }
+            val sY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.9f).apply {
+                interpolator = scaleInterp
+                duration = 250
+            }
+            val vA = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f).apply {
+                interpolator = linearInterp
+                duration = 100
+                startDelay = 150
+            }
+            val tA = ObjectAnimator.ofFloat(text, "alpha", 1f, 0f).apply {
+                interpolator = linearInterp
+                duration = 166
+            }
+            val iA = ObjectAnimator.ofFloat(icon, "alpha", 1f, 0f).apply {
+                interpolator = linearInterp
+                duration = 166
+            }
+            return AnimatorSet().apply {
+                playTogether(sX, sY, vA, tA, iA)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
index d8cb61c..8b782d4 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
@@ -17,6 +17,7 @@
 package com.android.systemui.toast;
 
 import android.content.Context;
+import android.view.LayoutInflater;
 
 import androidx.annotation.NonNull;
 
@@ -26,6 +27,7 @@
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.ToastPlugin;
 import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.FeatureFlags;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -40,10 +42,18 @@
 public class ToastFactory implements Dumpable {
     // only one ToastPlugin can be connected at a time.
     private ToastPlugin mPlugin;
+    private final LayoutInflater mLayoutInflater;
+    private final boolean mToastStyleEnabled;
 
     @Inject
-    public ToastFactory(PluginManager pluginManager, DumpManager dumpManager) {
+    public ToastFactory(
+            LayoutInflater layoutInflater,
+            PluginManager pluginManager,
+            DumpManager dumpManager,
+            FeatureFlags featureFlags) {
+        mLayoutInflater = layoutInflater;
         dumpManager.registerDumpable("ToastFactory", this);
+        mToastStyleEnabled = featureFlags.isToastStyleEnabled();
         pluginManager.addPluginListener(
                 new PluginListener<ToastPlugin>() {
                     @Override
@@ -64,11 +74,13 @@
      * Create a toast to be shown by ToastUI.
      */
     public SystemUIToast createToast(Context context, CharSequence text, String packageName,
-            int userId) {
+            int userId, int orientation) {
         if (isPluginAvailable()) {
-            return new SystemUIToast(context, text, mPlugin.createToast(text, packageName, userId));
+            return new SystemUIToast(mLayoutInflater, context, text, mPlugin.createToast(text,
+                    packageName, userId), packageName, userId, mToastStyleEnabled, orientation);
         }
-        return new SystemUIToast(context, text);
+        return new SystemUIToast(mLayoutInflater, context, text, packageName, userId,
+                mToastStyleEnabled, orientation);
     }
 
     private boolean isPluginAvailable() {
@@ -79,5 +91,6 @@
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("ToastFactory:");
         pw.println("    mAttachedPlugin=" + mPlugin);
+        pw.println("    mToastStyleEnabled=" + mToastStyleEnabled);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
index 78173cf..51541bd 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
@@ -49,6 +49,15 @@
         })
     }
 
+    fun logOrientationChange(text: String, isPortrait: Boolean) {
+        log(DEBUG, {
+            str1 = text
+            bool1 = isPortrait
+        }, {
+            "Orientation change for toast. msg=\'$str1\' isPortrait=$bool1"
+        })
+    }
+
     private inline fun log(
         logLevel: LogLevel,
         initializer: LogMessage.() -> Unit,
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index 409d136..92ea1d0 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -16,27 +16,28 @@
 
 package com.android.systemui.toast;
 
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
 import android.animation.Animator;
 import android.annotation.MainThread;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.INotificationManager;
 import android.app.ITransientNotificationCallback;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.os.IBinder;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.util.Log;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IAccessibilityManager;
-import android.widget.Toast;
 import android.widget.ToastPresenter;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.SystemUI;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.util.Objects;
 
@@ -58,18 +59,19 @@
     private final IAccessibilityManager mIAccessibilityManager;
     private final AccessibilityManager mAccessibilityManager;
     private final ToastFactory mToastFactory;
-    private final DelayableExecutor mMainExecutor;
     private final ToastLogger mToastLogger;
     private SystemUIToast mToast;
     @Nullable private ToastPresenter mPresenter;
     @Nullable private ITransientNotificationCallback mCallback;
+    private ToastOutAnimatorListener mToastOutAnimatorListener;
+
+    private int mOrientation = ORIENTATION_PORTRAIT;
 
     @Inject
     public ToastUI(
             Context context,
             CommandQueue commandQueue,
             ToastFactory toastFactory,
-            @Main DelayableExecutor mainExecutor,
             ToastLogger toastLogger) {
         this(context, commandQueue,
                 INotificationManager.Stub.asInterface(
@@ -77,21 +79,19 @@
                 IAccessibilityManager.Stub.asInterface(
                         ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)),
                 toastFactory,
-                mainExecutor,
                 toastLogger);
     }
 
     @VisibleForTesting
     ToastUI(Context context, CommandQueue commandQueue, INotificationManager notificationManager,
             @Nullable IAccessibilityManager accessibilityManager,
-            ToastFactory toastFactory, DelayableExecutor mainExecutor, ToastLogger toastLogger
+            ToastFactory toastFactory, ToastLogger toastLogger
     ) {
         super(context);
         mCommandQueue = commandQueue;
         mNotificationManager = notificationManager;
         mIAccessibilityManager = accessibilityManager;
         mToastFactory = toastFactory;
-        mMainExecutor = mainExecutor;
         mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
         mToastLogger = toastLogger;
     }
@@ -105,36 +105,38 @@
     @MainThread
     public void showToast(int uid, String packageName, IBinder token, CharSequence text,
             IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) {
-        if (mPresenter != null) {
-            hideCurrentToast();
-        }
-        UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
-        Context context = mContext.createContextAsUser(userHandle, 0);
-        mToast = mToastFactory.createToast(context, text, packageName, userHandle.getIdentifier());
+        Runnable showToastRunnable = () -> {
+            UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
+            Context context = mContext.createContextAsUser(userHandle, 0);
+            mToast = mToastFactory.createToast(mContext /* sysuiContext */, text, packageName,
+                    userHandle.getIdentifier(), mOrientation);
 
-        if (mToast.hasCustomAnimation()) {
             if (mToast.getInAnimation() != null) {
                 mToast.getInAnimation().start();
             }
-            final Animator hideAnimator = mToast.getOutAnimation();
-            if (hideAnimator != null) {
-                final long durationMillis = duration == Toast.LENGTH_LONG
-                        ? TOAST_LONG_TIME : TOAST_SHORT_TIME;
-                final long updatedDuration = mAccessibilityManager.getRecommendedTimeoutMillis(
-                        (int) durationMillis, AccessibilityManager.FLAG_CONTENT_TEXT);
-                mMainExecutor.executeDelayed(() -> hideAnimator.start(),
-                        updatedDuration - hideAnimator.getTotalDuration());
-            }
+
+            mCallback = callback;
+            mPresenter = new ToastPresenter(context, mIAccessibilityManager,
+                    mNotificationManager, packageName);
+            // Set as trusted overlay so touches can pass through toasts
+            mPresenter.getLayoutParams().setTrustedOverlay();
+            mToastLogger.logOnShowToast(uid, packageName, text.toString(), token.toString());
+            mPresenter.show(mToast.getView(), token, windowToken, duration, mToast.getGravity(),
+                    mToast.getXOffset(), mToast.getYOffset(), mToast.getHorizontalMargin(),
+                    mToast.getVerticalMargin(), mCallback, mToast.hasCustomAnimation());
+        };
+
+        if (mToastOutAnimatorListener != null) {
+            // if we're currently animating out a toast, show new toast after prev toast is hidden
+            mToastOutAnimatorListener.setShowNextToastRunnable(showToastRunnable);
+        } else if (mPresenter != null) {
+            // if there's a toast already showing that we haven't tried hiding yet, hide it and
+            // then show the next toast after its hidden animation is done
+            hideCurrentToast(showToastRunnable);
+        } else {
+            // else, show this next toast immediately
+            showToastRunnable.run();
         }
-        mCallback = callback;
-        mPresenter = new ToastPresenter(context, mIAccessibilityManager, mNotificationManager,
-                packageName);
-        // Set as trusted overlay so touches can pass through toasts
-        mPresenter.getLayoutParams().setTrustedOverlay();
-        mToastLogger.logOnShowToast(uid, packageName, text.toString(), token.toString());
-        mPresenter.show(mToast.getView(), token, windowToken, duration, mToast.getGravity(),
-                mToast.getXOffset(), mToast.getYOffset(), mToast.getHorizontalMargin(),
-                mToast.getVerticalMargin(), mCallback, mToast.hasCustomAnimation());
     }
 
     @Override
@@ -146,12 +148,88 @@
             return;
         }
         mToastLogger.logOnHideToast(packageName, token.toString());
-        hideCurrentToast();
+        hideCurrentToast(null);
     }
 
     @MainThread
-    private void hideCurrentToast() {
-        mPresenter.hide(mCallback);
+    private void hideCurrentToast(Runnable runnable) {
+        if (mToast.getOutAnimation() != null) {
+            Animator animator = mToast.getOutAnimation();
+            mToastOutAnimatorListener = new ToastOutAnimatorListener(mPresenter, mCallback,
+                    runnable);
+            animator.addListener(mToastOutAnimatorListener);
+            animator.start();
+        } else {
+            mPresenter.hide(mCallback);
+            if (runnable != null) {
+                runnable.run();
+            }
+        }
+        mToast = null;
         mPresenter = null;
+        mCallback = null;
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        if (newConfig.orientation != mOrientation) {
+            mOrientation = newConfig.orientation;
+            if (mToast != null) {
+                mToastLogger.logOrientationChange(mToast.mText.toString(),
+                        mOrientation == ORIENTATION_PORTRAIT);
+                mToast.onOrientationChange(mOrientation);
+                mPresenter.updateLayoutParams(
+                        mToast.getXOffset(),
+                        mToast.getYOffset(),
+                        mToast.getHorizontalMargin(),
+                        mToast.getVerticalMargin(),
+                        mToast.getGravity());
+            }
+        }
+    }
+
+    /**
+     * Once the out animation for a toast is finished, start showing the next toast.
+     */
+    class ToastOutAnimatorListener implements Animator.AnimatorListener {
+        final ToastPresenter mPrevPresenter;
+        final ITransientNotificationCallback mPrevCallback;
+        @Nullable Runnable mShowNextToastRunnable;
+
+        ToastOutAnimatorListener(
+                @NonNull ToastPresenter presenter,
+                @NonNull ITransientNotificationCallback callback,
+                @Nullable Runnable runnable) {
+            mPrevPresenter = presenter;
+            mPrevCallback = callback;
+            mShowNextToastRunnable = runnable;
+        }
+
+        void setShowNextToastRunnable(Runnable runnable) {
+            mShowNextToastRunnable = runnable;
+        }
+
+        @Override
+        public void onAnimationStart(Animator animation) {
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mPrevPresenter.hide(mPrevCallback);
+            if (mShowNextToastRunnable != null) {
+                mShowNextToastRunnable.run();
+            }
+            mToastOutAnimatorListener = null;
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            onAnimationEnd(animation);
+        }
+
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index 72f1f22..fd3641c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -20,12 +20,15 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.view.View;
 
+import com.android.systemui.R;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
 
 import java.util.HashSet;
 import java.util.List;
@@ -163,4 +166,15 @@
         }
         return apps;
     }
+
+    /**
+     * Returns true if the device should use the split notification shade, based on feature flags,
+     * orientation and screen width.
+     */
+    public static boolean shouldUseSplitNotificationShade(FeatureFlags featureFlags,
+            Resources resources) {
+        return featureFlags.isTwoColumnNotificationShadeEnabled()
+                && resources.getBoolean(R.bool.config_use_split_notification_shade);
+    }
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
index 0795d89..ff28819 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.os.Handler;
 
+import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.WMSingleton;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
@@ -28,6 +29,7 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
@@ -49,7 +51,7 @@
 import dagger.Provides;
 
 /**
- * Dagger module for TV Pip.
+ * Provides TV specific dependencies for Pip.
  */
 @Module(includes = {WMShellBaseModule.class})
 public abstract class TvPipModule {
@@ -143,7 +145,8 @@
             PipAnimationController pipAnimationController,
             PipTransitionController pipTransitionController,
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
-            Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController,
+            Optional<LegacySplitScreenController> splitScreenOptional,
+            DisplayController displayController,
             PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm,
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
index f23367b..141b9f7 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.view.IWindowManager;
 
+import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.WMSingleton;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
@@ -39,11 +40,20 @@
 import dagger.Provides;
 
 /**
- * Provides dependencies from {@link com.android.wm.shell} which could be customized among different
- * branches of SystemUI.
+ * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only
+ * accessible from components within the WM subcomponent (can be explicitly exposed to the
+ * SysUIComponent, see {@link WMComponent}).
+ *
+ * This module only defines Shell dependencies for the TV SystemUI implementation.  Common
+ * dependencies should go into {@link WMShellBaseModule}.
  */
 @Module(includes = {TvPipModule.class})
 public class TvWMShellModule {
+
+    //
+    // Internal common - Components used internally by multiple shell features
+    //
+
     @WMSingleton
     @Provides
     static DisplayImeController provideDisplayImeController(IWindowManager wmService,
@@ -53,16 +63,20 @@
                 transactionPool);
     }
 
+    //
+    // Split/multiwindow
+    //
+
     @WMSingleton
     @Provides
-    static LegacySplitScreen provideSplitScreen(Context context,
+    static LegacySplitScreenController provideSplitScreen(Context context,
             DisplayController displayController, SystemWindows systemWindows,
             DisplayImeController displayImeController, TransactionPool transactionPool,
             ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
             TaskStackListenerImpl taskStackListener, Transitions transitions,
             @ShellMainThread ShellExecutor mainExecutor,
             @ChoreographerSfVsync AnimationHandler sfVsyncAnimationHandler) {
-        return LegacySplitScreenController.create(context, displayController, systemWindows,
+        return new LegacySplitScreenController(context, displayController, systemWindows,
                 displayImeController, transactionPool, shellTaskOrganizer, syncQueue,
                 taskStackListener, transitions, mainExecutor, sfVsyncAnimationHandler);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 81ac21c..ec61db5 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -43,6 +43,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.SystemUI;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.model.SysUiState;
@@ -73,7 +74,20 @@
 import javax.inject.Inject;
 
 /**
- * Proxy in SysUiScope to delegate events to controllers in WM Shell library.
+ * A SystemUI service that starts with the SystemUI application and sets up any bindings between
+ * Shell and SysUI components.  This service starts happens after the {@link WMComponent} has
+ * already been initialized and may only reference Shell components that are explicitly exported to
+ * SystemUI (see {@link WMComponent}.
+ *
+ * eg. SysUI application starts
+ *     -> SystemUIFactory is initialized
+ *       -> WMComponent is created
+ *         -> WMShellBaseModule dependencies are injected
+ *         -> WMShellModule (form-factory specific) dependencies are injected
+ *       -> SysUIComponent is created
+ *         -> WMComponents are explicitly provided to SysUIComponent for injection into SysUI code
+ *     -> SysUI services are started
+ *       -> WMShell starts and binds SysUI with Shell components via exported Shell interfaces
  */
 @SysUISingleton
 public final class WMShell extends SystemUI
@@ -142,6 +156,8 @@
 
     @Override
     public void start() {
+        // TODO: Consider piping config change and other common calls to a shell component to
+        //  delegate internally
         mProtoTracer.add(this);
         mCommandQueue.addCallback(this);
         mPipOptional.ifPresent(this::initPip);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index b42dde6..449db61 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -32,6 +32,7 @@
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.WMSingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.wm.shell.FullscreenTaskListener;
@@ -45,6 +46,7 @@
 import com.android.wm.shell.TaskViewFactoryController;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.apppairs.AppPairsController;
 import com.android.wm.shell.bubbles.BubbleController;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.common.DisplayController;
@@ -63,6 +65,7 @@
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.Pip;
@@ -70,8 +73,8 @@
 import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
 import com.android.wm.shell.pip.PipUiEventLogger;
 import com.android.wm.shell.pip.phone.PipAppOpsListener;
+import com.android.wm.shell.pip.phone.PipController;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
 import com.android.wm.shell.sizecompatui.SizeCompatUIController;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -85,8 +88,13 @@
 import dagger.Provides;
 
 /**
- * Provides basic dependencies from {@link com.android.wm.shell}, the dependencies declared here
- * should be shared among different branches of SystemUI.
+ * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
+ * accessible from components within the WM subcomponent (can be explicitly exposed to the
+ * SysUIComponent, see {@link WMComponent}).
+ *
+ * This module only defines *common* dependencies across various SystemUI implementations,
+ * dependencies that are device/form factor SystemUI implementation specific should go into their
+ * respective modules (ie. {@link WMShellModule} for handheld, {@link TvWMShellModule} for tv, etc.)
  */
 @Module
 public abstract class WMShellBaseModule {
@@ -174,53 +182,9 @@
         }
     }
 
-    @WMSingleton
-    @Provides
-    static ShellInit provideShellInit(DisplayImeController displayImeController,
-            DragAndDropController dragAndDropController,
-            ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
-            Optional<SplitScreenController> splitScreenOptional,
-            Optional<AppPairs> appPairsOptional,
-            FullscreenTaskListener fullscreenTaskListener,
-            Transitions transitions,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return ShellInitImpl.create(displayImeController,
-                dragAndDropController,
-                shellTaskOrganizer,
-                legacySplitScreenOptional,
-                splitScreenOptional,
-                appPairsOptional,
-                fullscreenTaskListener,
-                transitions,
-                mainExecutor);
-    }
-
-    /**
-     * Note, this is only optional because we currently pass this to the SysUI component scope and
-     * for non-primary users, we may inject a null-optional for that dependency.
-     */
-    @WMSingleton
-    @Provides
-    static Optional<ShellCommandHandler> provideShellCommandHandler(
-            ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
-            Optional<SplitScreenController> splitScreenOptional,
-            Optional<Pip> pipOptional,
-            Optional<OneHanded> oneHandedOptional,
-            Optional<HideDisplayCutout> hideDisplayCutout,
-            Optional<AppPairs> appPairsOptional,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return Optional.of(ShellCommandHandlerImpl.create(shellTaskOrganizer,
-                legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional,
-                hideDisplayCutout, appPairsOptional, mainExecutor));
-    }
-
-    @WMSingleton
-    @Provides
-    static TransactionPool provideTransactionPool() {
-        return new TransactionPool();
-    }
+    //
+    // Internal common - Components used internally by multiple shell features
+    //
 
     @WMSingleton
     @Provides
@@ -238,8 +202,45 @@
 
     @WMSingleton
     @Provides
-    static FloatingContentCoordinator provideFloatingContentCoordinator() {
-        return new FloatingContentCoordinator();
+    static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
+            Context context, SizeCompatUIController sizeCompatUI) {
+        return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI);
+    }
+
+    @WMSingleton
+    @Provides
+    static SizeCompatUIController provideSizeCompatUIController(Context context,
+            DisplayController displayController, DisplayImeController imeController,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new SizeCompatUIController(context, displayController, imeController, mainExecutor);
+    }
+
+    @WMSingleton
+    @Provides
+    static SyncTransactionQueue provideSyncTransactionQueue(TransactionPool pool,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new SyncTransactionQueue(pool, mainExecutor);
+    }
+
+    @WMSingleton
+    @Provides
+    static SystemWindows provideSystemWindows(DisplayController displayController,
+            IWindowManager wmService) {
+        return new SystemWindows(displayController, wmService);
+    }
+
+    // We currently dedupe multiple messages, so we use the shell main handler directly
+    @WMSingleton
+    @Provides
+    static TaskStackListenerImpl providerTaskStackListenerImpl(
+            @ShellMainThread Handler mainHandler) {
+        return new TaskStackListenerImpl(mainHandler);
+    }
+
+    @WMSingleton
+    @Provides
+    static TransactionPool provideTransactionPool() {
+        return new TransactionPool();
     }
 
     @WMSingleton
@@ -249,10 +250,99 @@
         return new WindowManagerShellWrapper(mainExecutor);
     }
 
+    //
+    // Bubbles
+    //
+
+    @WMSingleton
+    @Provides
+    static Optional<Bubbles> provideBubbles(Optional<BubbleController> bubbleController) {
+        return bubbleController.map((controller) -> controller.asBubbles());
+    }
+
+    // Note: Handler needed for LauncherApps.register
+    @WMSingleton
+    @Provides
+    static Optional<BubbleController> provideBubbleController(Context context,
+            FloatingContentCoordinator floatingContentCoordinator,
+            IStatusBarService statusBarService,
+            WindowManager windowManager,
+            WindowManagerShellWrapper windowManagerShellWrapper,
+            LauncherApps launcherApps,
+            UiEventLogger uiEventLogger,
+            ShellTaskOrganizer organizer,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellMainThread Handler mainHandler) {
+        return Optional.of(BubbleController.create(context, null /* synchronizer */,
+                floatingContentCoordinator, statusBarService, windowManager,
+                windowManagerShellWrapper, launcherApps, uiEventLogger, organizer,
+                mainExecutor, mainHandler));
+    }
+
+    //
+    // Fullscreen
+    //
+
+    @WMSingleton
+    @Provides
+    static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) {
+        return new FullscreenTaskListener(syncQueue);
+    }
+
+    //
+    // Hide display cutout
+    //
+
+    @WMSingleton
+    @Provides
+    static Optional<HideDisplayCutout> provideHideDisplayCutout(
+            Optional<HideDisplayCutoutController> hideDisplayCutoutController) {
+        return hideDisplayCutoutController.map((controller) -> controller.asHideDisplayCutout());
+    }
+
+    @WMSingleton
+    @Provides
+    static Optional<HideDisplayCutoutController> provideHideDisplayCutoutController(Context context,
+            DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor) {
+        return Optional.ofNullable(
+                HideDisplayCutoutController.create(context, displayController, mainExecutor));
+    }
+
+    //
+    // One handed mode (optional feature)
+    //
+
+    @WMSingleton
+    @Provides
+    static Optional<OneHanded> provideOneHanded(Optional<OneHandedController> oneHandedController) {
+        return oneHandedController.map((controller) -> controller.asOneHanded());
+    }
+
+    // Needs the shell main handler for ContentObserver callbacks
+    @WMSingleton
+    @Provides
+    static Optional<OneHandedController> provideOneHandedController(Context context,
+            DisplayController displayController, TaskStackListenerImpl taskStackListener,
+            UiEventLogger uiEventLogger,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellMainThread Handler mainHandler) {
+        return Optional.ofNullable(OneHandedController.create(context, displayController,
+                taskStackListener, uiEventLogger, mainExecutor, mainHandler));
+    }
+
+    //
+    // Pip (optional feature)
+    //
+
+    @WMSingleton
+    @Provides
+    static FloatingContentCoordinator provideFloatingContentCoordinator() {
+        return new FloatingContentCoordinator();
+    }
+
     @WMSingleton
     @Provides
     static PipAppOpsListener providePipAppOpsListener(Context context,
-            IActivityManager activityManager,
             PipTouchHandler pipTouchHandler,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor);
@@ -268,37 +358,38 @@
 
     @WMSingleton
     @Provides
-    static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger,
-            PackageManager packageManager) {
-        return new PipUiEventLogger(uiEventLogger, packageManager);
-    }
-
-    @WMSingleton
-    @Provides
     static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) {
         return new PipSurfaceTransactionHelper(context);
     }
 
     @WMSingleton
     @Provides
-    static SystemWindows provideSystemWindows(DisplayController displayController,
-            IWindowManager wmService) {
-        return new SystemWindows(displayController, wmService);
+    static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger,
+            PackageManager packageManager) {
+        return new PipUiEventLogger(uiEventLogger, packageManager);
+    }
+
+    //
+    // Shell transitions
+    //
+
+    @WMSingleton
+    @Provides
+    static RemoteTransitions provideRemoteTransitions(Transitions transitions) {
+        return Transitions.asRemoteTransitions(transitions);
     }
 
     @WMSingleton
     @Provides
-    static SyncTransactionQueue provideSyncTransactionQueue(TransactionPool pool,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return new SyncTransactionQueue(pool, mainExecutor);
+    static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellAnimationThread ShellExecutor animExecutor) {
+        return new Transitions(organizer, pool, mainExecutor, animExecutor);
     }
 
-    @WMSingleton
-    @Provides
-    static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
-            Context context, SizeCompatUI sizeCompatUI) {
-        return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI);
-    }
+    //
+    // Split/multiwindow
+    //
 
     @WMSingleton
     @Provides
@@ -307,17 +398,6 @@
         return new RootTaskDisplayAreaOrganizer(mainExecutor, context);
     }
 
-    // We currently dedupe multiple messages, so we use the shell main handler directly
-    @WMSingleton
-    @Provides
-    static TaskStackListenerImpl providerTaskStackListenerImpl(
-            @ShellMainThread Handler mainHandler) {
-        return new TaskStackListenerImpl(mainHandler);
-    }
-
-    @BindsOptionalOf
-    abstract LegacySplitScreen optionalLegacySplitScreen();
-
     @WMSingleton
     @Provides
     static Optional<SplitScreen> provideSplitScreen(
@@ -340,81 +420,91 @@
         }
     }
 
+    // Legacy split (optional feature)
+
+    @WMSingleton
+    @Provides
+    static Optional<LegacySplitScreen> provideLegacySplitScreen(
+            Optional<LegacySplitScreenController> splitScreenController) {
+        return splitScreenController.map((controller) -> controller.asLegacySplitScreen());
+    }
+
     @BindsOptionalOf
-    abstract AppPairs optionalAppPairs();
+    abstract LegacySplitScreenController optionalLegacySplitScreenController();
 
-    // Note: Handler needed for LauncherApps.register
+    // App Pairs (optional feature)
+
     @WMSingleton
     @Provides
-    static Optional<Bubbles> provideBubbles(Context context,
-            FloatingContentCoordinator floatingContentCoordinator,
-            IStatusBarService statusBarService,
-            WindowManager windowManager,
-            WindowManagerShellWrapper windowManagerShellWrapper,
-            LauncherApps launcherApps,
-            UiEventLogger uiEventLogger,
-            ShellTaskOrganizer organizer,
-            @ShellMainThread ShellExecutor mainExecutor,
-            @ShellMainThread Handler mainHandler) {
-        return Optional.of(BubbleController.create(context, null /* synchronizer */,
-                floatingContentCoordinator, statusBarService, windowManager,
-                windowManagerShellWrapper, launcherApps, uiEventLogger, organizer,
-                mainExecutor, mainHandler));
+    static Optional<AppPairs> provideAppPairs(Optional<AppPairsController> appPairsController) {
+        return appPairsController.map((controller) -> controller.asAppPairs());
     }
 
-    // Needs the shell main handler for ContentObserver callbacks
+    @BindsOptionalOf
+    abstract AppPairsController optionalAppPairs();
+
+    //
+    // Task view factory
+    //
+
     @WMSingleton
     @Provides
-    static Optional<OneHanded> provideOneHandedController(Context context,
-            DisplayController displayController, TaskStackListenerImpl taskStackListener,
-            UiEventLogger uiEventLogger,
-            @ShellMainThread ShellExecutor mainExecutor,
-            @ShellMainThread Handler mainHandler) {
-        return Optional.ofNullable(OneHandedController.create(context, displayController,
-                taskStackListener, uiEventLogger, mainExecutor, mainHandler));
+    static Optional<TaskViewFactory> provideTaskViewFactory(
+            TaskViewFactoryController taskViewFactoryController) {
+        return Optional.of(taskViewFactoryController.asTaskViewFactory());
     }
 
     @WMSingleton
     @Provides
-    static Optional<HideDisplayCutout> provideHideDisplayCutoutController(Context context,
-            DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor) {
-        return Optional.ofNullable(
-                HideDisplayCutoutController.create(context, displayController, mainExecutor));
-    }
-
-    @WMSingleton
-    @Provides
-    static Optional<TaskViewFactory> provideTaskViewFactory(ShellTaskOrganizer shellTaskOrganizer,
+    static TaskViewFactoryController provideTaskViewFactoryController(
+            ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
-        return Optional.of(new TaskViewFactoryController(shellTaskOrganizer, mainExecutor)
-                .getTaskViewFactory());
+        return new TaskViewFactoryController(shellTaskOrganizer, mainExecutor);
     }
 
+    //
+    // Misc
+    //
+
     @WMSingleton
     @Provides
-    static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) {
-        return new FullscreenTaskListener(syncQueue);
-    }
-
-    @WMSingleton
-    @Provides
-    static RemoteTransitions provideRemoteTransitions(Transitions transitions) {
-        return Transitions.asRemoteTransitions(transitions);
-    }
-
-    @WMSingleton
-    @Provides
-    static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
-            @ShellMainThread ShellExecutor mainExecutor,
-            @ShellAnimationThread ShellExecutor animExecutor) {
-        return new Transitions(organizer, pool, mainExecutor, animExecutor);
-    }
-
-    @WMSingleton
-    @Provides
-    static SizeCompatUI provideSizeCompatUI(Context context, DisplayController displayController,
-            DisplayImeController imeController, @ShellMainThread ShellExecutor mainExecutor) {
-        return SizeCompatUIController.create(context, displayController, imeController,
+    static ShellInit provideShellInit(DisplayImeController displayImeController,
+            DragAndDropController dragAndDropController,
+            ShellTaskOrganizer shellTaskOrganizer,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
+            Optional<SplitScreenController> splitScreenOptional,
+            Optional<AppPairsController> appPairsOptional,
+            FullscreenTaskListener fullscreenTaskListener,
+            Transitions transitions,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return ShellInitImpl.create(displayImeController,
+                dragAndDropController,
+                shellTaskOrganizer,
+                legacySplitScreenOptional,
+                splitScreenOptional,
+                appPairsOptional,
+                fullscreenTaskListener,
+                transitions,
                 mainExecutor);
     }
+
+    /**
+     * Note, this is only optional because we currently pass this to the SysUI component scope and
+     * for non-primary users, we may inject a null-optional for that dependency.
+     */
+    @WMSingleton
+    @Provides
+    static Optional<ShellCommandHandler> provideShellCommandHandler(
+            ShellTaskOrganizer shellTaskOrganizer,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
+            Optional<SplitScreenController> splitScreenOptional,
+            Optional<Pip> pipOptional,
+            Optional<OneHandedController> oneHandedOptional,
+            Optional<HideDisplayCutoutController> hideDisplayCutout,
+            Optional<AppPairsController> appPairsOptional,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return Optional.of(ShellCommandHandlerImpl.create(shellTaskOrganizer,
+                legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional,
+                hideDisplayCutout, appPairsOptional, mainExecutor));
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index 2aaa095..997b488 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -21,10 +21,10 @@
 import android.os.Handler;
 import android.view.IWindowManager;
 
+import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.WMSingleton;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
-import com.android.wm.shell.apppairs.AppPairs;
 import com.android.wm.shell.apppairs.AppPairsController;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
@@ -36,7 +36,6 @@
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
 import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
@@ -60,11 +59,20 @@
 import dagger.Provides;
 
 /**
- * Provides dependencies from {@link com.android.wm.shell} which could be customized among different
- * branches of SystemUI.
+ * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only
+ * accessible from components within the WM subcomponent (can be explicitly exposed to the
+ * SysUIComponent, see {@link WMComponent}).
+ *
+ * This module only defines Shell dependencies for handheld SystemUI implementation.  Common
+ * dependencies should go into {@link WMShellBaseModule}.
  */
 @Module(includes = WMShellBaseModule.class)
 public class WMShellModule {
+
+    //
+    // Internal common - Components used internally by multiple shell features
+    //
+
     @WMSingleton
     @Provides
     static DisplayImeController provideDisplayImeController(IWindowManager wmService,
@@ -74,29 +82,37 @@
                 transactionPool);
     }
 
+    //
+    // Split/multiwindow
+    //
+
     @WMSingleton
     @Provides
-    static LegacySplitScreen provideLegacySplitScreen(Context context,
+    static LegacySplitScreenController provideLegacySplitScreen(Context context,
             DisplayController displayController, SystemWindows systemWindows,
             DisplayImeController displayImeController, TransactionPool transactionPool,
             ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
             TaskStackListenerImpl taskStackListener, Transitions transitions,
             @ShellMainThread ShellExecutor mainExecutor,
             @ChoreographerSfVsync AnimationHandler sfVsyncAnimationHandler) {
-        return LegacySplitScreenController.create(context, displayController, systemWindows,
+        return new LegacySplitScreenController(context, displayController, systemWindows,
                 displayImeController, transactionPool, shellTaskOrganizer, syncQueue,
                 taskStackListener, transitions, mainExecutor, sfVsyncAnimationHandler);
     }
 
     @WMSingleton
     @Provides
-    static AppPairs provideAppPairs(ShellTaskOrganizer shellTaskOrganizer,
+    static AppPairsController provideAppPairs(ShellTaskOrganizer shellTaskOrganizer,
             SyncTransactionQueue syncQueue, DisplayController displayController,
             @ShellMainThread ShellExecutor mainExecutor) {
-        return AppPairsController.create(shellTaskOrganizer, syncQueue, displayController,
+        return new AppPairsController(shellTaskOrganizer, syncQueue, displayController,
                 mainExecutor);
     }
 
+    //
+    // Pip
+    //
+
     @WMSingleton
     @Provides
     static Optional<Pip> providePip(Context context, DisplayController displayController,
@@ -161,7 +177,8 @@
             PipAnimationController pipAnimationController,
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
             PipTransitionController pipTransitionController,
-            Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController,
+            Optional<LegacySplitScreenController> splitScreenOptional,
+            DisplayController displayController,
             PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
new file mode 100644
index 0000000..9278570
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.ui
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.globalactions.GlobalActionsComponent
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.wm.shell.TaskViewFactory
+import dagger.Lazy
+import java.util.Optional
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Answers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlActionCoordinatorImplTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var uiController: ControlsUiController
+    @Mock
+    private lateinit var lazyUiController: Lazy<ControlsUiController>
+    @Mock
+    private lateinit var keyguardStateController: KeyguardStateController
+    @Mock
+    private lateinit var bgExecutor: DelayableExecutor
+    @Mock
+    private lateinit var uiExecutor: DelayableExecutor
+    @Mock
+    private lateinit var activityStarter: ActivityStarter
+    @Mock
+    private lateinit var globalActionsComponent: GlobalActionsComponent
+    @Mock
+    private lateinit var taskViewFactory: Optional<TaskViewFactory>
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private lateinit var cvh: ControlViewHolder
+
+    companion object {
+        fun <T> any(): T = Mockito.any<T>()
+
+        private val ID = "id"
+    }
+
+    private lateinit var coordinator: ControlActionCoordinatorImpl
+    private lateinit var action: ControlActionCoordinatorImpl.Action
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        coordinator = spy(ControlActionCoordinatorImpl(
+            mContext,
+            bgExecutor,
+            uiExecutor,
+            activityStarter,
+            keyguardStateController,
+            globalActionsComponent,
+            taskViewFactory,
+            getFakeBroadcastDispatcher(),
+            lazyUiController
+        ))
+
+        `when`(cvh.cws.ci.controlId).thenReturn(ID)
+        action = spy(coordinator.Action(ID, {}, false))
+        doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean())
+    }
+
+    @Test
+    fun testToggleRunsWhenUnlocked() {
+        `when`(keyguardStateController.isShowing()).thenReturn(false)
+
+        coordinator.toggle(cvh, "", true)
+        verify(coordinator).bouncerOrRun(action)
+        verify(action).invoke()
+    }
+
+    @Test
+    fun testToggleDoesNotRunWhenLockedThenRunsWhenUnlocked() {
+        `when`(keyguardStateController.isShowing()).thenReturn(true)
+        `when`(keyguardStateController.isUnlocked()).thenReturn(false)
+
+        coordinator.toggle(cvh, "", true)
+        verify(coordinator).bouncerOrRun(action)
+        verify(activityStarter).dismissKeyguardThenExecute(any(), any(), anyBoolean())
+        verify(action, never()).invoke()
+
+        // Simulate a refresh call from a Publisher, which will trigger a call to runPendingAction
+        reset(action)
+        coordinator.runPendingAction(ID)
+        verify(action, never()).invoke()
+
+        `when`(keyguardStateController.isUnlocked()).thenReturn(true)
+        reset(action)
+        coordinator.runPendingAction(ID)
+        verify(action).invoke()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 0dc268a..fb817ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.qs;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -28,6 +30,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 
@@ -37,6 +41,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.MediaHost;
@@ -44,6 +49,7 @@
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.util.animation.DisappearParameters;
 
 import org.junit.Before;
@@ -86,17 +92,25 @@
     QSTileView mQSTileView;
     @Mock
     PagedTileLayout mPagedTileLayout;
+    @Mock
+    FeatureFlags mFeatureFlags;
+    @Mock
+    Resources mResources;
+    @Mock
+    Configuration mConfiguration;
 
     private QSPanelControllerBase<QSPanel> mController;
 
+
+
     /** Implementation needed to ensure we have a reflectively-available class name. */
     private class TestableQSPanelControllerBase extends QSPanelControllerBase<QSPanel> {
         protected TestableQSPanelControllerBase(QSPanel view, QSTileHost host,
                 QSCustomizerController qsCustomizerController, MediaHost mediaHost,
                 MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
-                DumpManager dumpManager) {
+                DumpManager dumpManager, FeatureFlags featureFlags) {
             super(view, host, qsCustomizerController, true, mediaHost, metricsLogger, uiEventLogger,
-                    qsLogger, dumpManager);
+                    qsLogger, dumpManager, featureFlags);
         }
 
         @Override
@@ -121,10 +135,12 @@
         when(mQSTileRevealControllerFactory.create(any(), any()))
                 .thenReturn(mQSTileRevealController);
         when(mMediaHost.getDisappearParameters()).thenReturn(new DisappearParameters());
+        when(mQSPanel.getResources()).thenReturn(mResources);
+        when(mResources.getConfiguration()).thenReturn(mConfiguration);
 
         mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
                 mQSCustomizerController, mMediaHost,
-                mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
+                mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags);
 
         mController.init();
         reset(mQSTileRevealController);
@@ -136,7 +152,7 @@
 
         QSPanelControllerBase<QSPanel> controller = new TestableQSPanelControllerBase(mQSPanel,
                 mQSTileHost, mQSCustomizerController, mMediaHost,
-                mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager) {
+                mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags) {
             @Override
             protected QSTileRevealController createTileRevealController() {
                 return mQSTileRevealController;
@@ -218,4 +234,18 @@
         verify(mQSLogger).logAllTilesChangeListening(false, "QSPanel", "dnd");
         verify(mPagedTileLayout).setListening(false, mUiEventLogger);
     }
+
+
+    @Test
+    public void testShouldUzeHorizontalLayout_falseForSplitShade() {
+        mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+        when(mMediaHost.getVisible()).thenReturn(true);
+
+        when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(false);
+        assertThat(mController.shouldUseHorizontalLayout()).isTrue();
+
+        when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
+        assertThat(mController.shouldUseHorizontalLayout()).isFalse();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
index 4381158..0dfebab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
@@ -41,6 +41,7 @@
 import com.android.systemui.settings.brightness.BrightnessController;
 import com.android.systemui.settings.brightness.BrightnessSlider;
 import com.android.systemui.settings.brightness.ToggleSlider;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.animation.DisappearParameters;
 
@@ -93,6 +94,8 @@
     QSTileView mQSTileView;
     @Mock
     PagedTileLayout mPagedTileLayout;
+    @Mock
+    FeatureFlags mFeatureFlags;
 
     private QSPanelController mController;
 
@@ -118,7 +121,9 @@
                 mQSTileHost, mQSCustomizerController, true, mMediaHost,
                 mQSTileRevealControllerFactory, mDumpManager, mMetricsLogger, mUiEventLogger,
                 mQSLogger, mBrightnessControllerFactory, mToggleSliderViewControllerFactory,
-                /* labelsFlag */ false);
+                /* labelsFlag */ false,
+                mFeatureFlags
+        );
 
         mController.init();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 107160f..5870200 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.plugins.qs.QSTileView
 import com.android.systemui.qs.customize.QSCustomizerController
 import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.statusbar.FeatureFlags
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -63,6 +64,8 @@
     private lateinit var tileLayout: TileLayout
     @Mock
     private lateinit var tileView: QSTileView
+    @Mock
+    private lateinit var featureFlags: FeatureFlags
 
     private lateinit var controller: QuickQSPanelController
 
@@ -84,7 +87,8 @@
                 uiEventLogger,
                 qsLogger,
                 dumpManager,
-                false
+                false,
+                featureFlags
         )
 
         controller.init()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
new file mode 100644
index 0000000..b7b9678
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles
+
+import android.os.Handler
+import android.provider.Settings
+import android.service.quicksettings.Tile
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.lifecycle.LifecycleOwner
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.ControlsDialog
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.statusbar.FeatureFlags
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.GlobalSettings
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class DeviceControlsTileTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var qsHost: QSHost
+    @Mock
+    private lateinit var metricsLogger: MetricsLogger
+    @Mock
+    private lateinit var statusBarStateController: StatusBarStateController
+    @Mock
+    private lateinit var activityStarter: ActivityStarter
+    @Mock
+    private lateinit var qsLogger: QSLogger
+    private lateinit var controlsComponent: ControlsComponent
+    @Mock
+    private lateinit var controlsUiController: ControlsUiController
+    @Mock
+    private lateinit var controlsListingController: ControlsListingController
+    @Mock
+    private lateinit var controlsController: ControlsController
+    @Mock
+    private lateinit var featureFlags: FeatureFlags
+    @Mock
+    private lateinit var controlsDialog: ControlsDialog
+    private lateinit var globalSettings: GlobalSettings
+    @Mock
+    private lateinit var serviceInfo: ControlsServiceInfo
+    @Mock
+    private lateinit var uiEventLogger: UiEventLogger
+    @Captor
+    private lateinit var listingCallbackCaptor:
+            ArgumentCaptor<ControlsListingController.ControlsListingCallback>
+
+    private lateinit var testableLooper: TestableLooper
+    private lateinit var tile: DeviceControlsTile
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        testableLooper = TestableLooper.get(this)
+
+        `when`(qsHost.context).thenReturn(mContext)
+        `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
+
+        controlsComponent = ControlsComponent(
+                true,
+                { controlsController },
+                { controlsUiController },
+                { controlsListingController }
+        )
+
+        globalSettings = FakeSettings()
+
+        globalSettings.putInt(DeviceControlsTile.SETTINGS_FLAG, 1)
+        `when`(featureFlags.isKeyguardLayoutEnabled).thenReturn(true)
+
+        tile = createTile()
+    }
+
+    @Test
+    fun testAvailable() {
+        assertThat(tile.isAvailable).isTrue()
+    }
+
+    @Test
+    fun testNotAvailableFeature() {
+        `when`(featureFlags.isKeyguardLayoutEnabled).thenReturn(false)
+
+        assertThat(tile.isAvailable).isFalse()
+    }
+
+    @Test
+    fun testNotAvailableControls() {
+        controlsComponent = ControlsComponent(
+                false,
+                { controlsController },
+                { controlsUiController },
+                { controlsListingController }
+        )
+        tile = createTile()
+
+        assertThat(tile.isAvailable).isFalse()
+    }
+
+    @Test
+    fun testNotAvailableFlag() {
+        globalSettings.putInt(DeviceControlsTile.SETTINGS_FLAG, 0)
+        tile = createTile()
+
+        assertThat(tile.isAvailable).isFalse()
+    }
+
+    @Test
+    fun testObservingCallback() {
+        verify(controlsListingController).observe(
+                any(LifecycleOwner::class.java),
+                any(ControlsListingController.ControlsListingCallback::class.java)
+        )
+    }
+
+    @Test
+    fun testLongClickIntent() {
+        assertThat(tile.longClickIntent.action).isEqualTo(Settings.ACTION_DEVICE_CONTROLS_SETTINGS)
+    }
+
+    @Test
+    fun testUnavailableByDefault() {
+        assertThat(tile.state.state).isEqualTo(Tile.STATE_UNAVAILABLE)
+    }
+
+    @Test
+    fun testStateUnavailableIfNoListings() {
+        verify(controlsListingController).observe(
+                any(LifecycleOwner::class.java),
+                capture(listingCallbackCaptor)
+        )
+
+        listingCallbackCaptor.value.onServicesUpdated(emptyList())
+        testableLooper.processAllMessages()
+
+        assertThat(tile.state.state).isEqualTo(Tile.STATE_UNAVAILABLE)
+    }
+
+    @Test
+    fun testStateAvailableIfListings() {
+        verify(controlsListingController).observe(
+                any(LifecycleOwner::class.java),
+                capture(listingCallbackCaptor)
+        )
+
+        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+        testableLooper.processAllMessages()
+
+        assertThat(tile.state.state).isEqualTo(Tile.STATE_ACTIVE)
+    }
+
+    @Test
+    fun testMoveBetweenStates() {
+        verify(controlsListingController).observe(
+                any(LifecycleOwner::class.java),
+                capture(listingCallbackCaptor)
+        )
+
+        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+        testableLooper.processAllMessages()
+
+        listingCallbackCaptor.value.onServicesUpdated(emptyList())
+        testableLooper.processAllMessages()
+
+        assertThat(tile.state.state).isEqualTo(Tile.STATE_UNAVAILABLE)
+    }
+
+    @Test
+    fun testNoDialogWhenUnavailable() {
+        tile.click()
+        testableLooper.processAllMessages()
+
+        verify(controlsDialog, never()).show(any(ControlsUiController::class.java))
+    }
+
+    @Test
+    fun testDialogShowWhenAvailable() {
+        verify(controlsListingController).observe(
+                any(LifecycleOwner::class.java),
+                capture(listingCallbackCaptor)
+        )
+
+        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+        testableLooper.processAllMessages()
+
+        tile.click()
+        testableLooper.processAllMessages()
+
+        verify(controlsDialog).show(controlsUiController)
+    }
+
+    @Test
+    fun testDialogDismissedOnDestroy() {
+        verify(controlsListingController).observe(
+                any(LifecycleOwner::class.java),
+                capture(listingCallbackCaptor)
+        )
+
+        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+        testableLooper.processAllMessages()
+
+        tile.click()
+        testableLooper.processAllMessages()
+
+        tile.destroy()
+        testableLooper.processAllMessages()
+        verify(controlsDialog).dismiss()
+    }
+
+    private fun createTile(): DeviceControlsTile {
+        return DeviceControlsTile(
+                qsHost,
+                testableLooper.looper,
+                Handler(testableLooper.looper),
+                metricsLogger,
+                statusBarStateController,
+                activityStarter,
+                qsLogger,
+                controlsComponent,
+                featureFlags,
+                { controlsDialog },
+                globalSettings
+        )
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index ee1d758..4162884 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -393,10 +393,11 @@
 
     private void positionClock() {
         mClockPositionAlgorithm.setup(EMPTY_MARGIN, SCREEN_HEIGHT, mNotificationStackHeight,
-                mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight, mPreferredClockY,
-                mHasCustomClock, mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */,
-                0 /* unlockedStackScrollerPadding */, false /* udfpsEnrolled */,
-                mQsExpansion, mCutoutTopInset);
+                mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight,
+                0 /* keyguardUserSwitcherHeight */, mPreferredClockY, mHasCustomClock,
+                mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */,
+                0 /* unlockedStackScrollerPadding */, false /* udfpsEnrolled */, mQsExpansion,
+                mCutoutTopInset);
         mClockPositionAlgorithm.run(mClockPosition);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index c07ba72..d0e7031 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -37,6 +37,7 @@
 import android.content.res.Resources;
 import android.hardware.biometrics.BiometricSourceType;
 import android.os.PowerManager;
+import android.os.UserManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.DisplayMetrics;
@@ -58,6 +59,7 @@
 import com.android.keyguard.KeyguardStatusViewController;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
+import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
@@ -68,7 +70,6 @@
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.media.MediaHierarchyManager;
-import com.android.systemui.qs.QSDetailDisplayer;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
@@ -193,6 +194,8 @@
     @Mock
     private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
     @Mock
+    private KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponent;
+    @Mock
     private KeyguardStatusViewComponent mKeyguardStatusViewComponent;
     @Mock
     private KeyguardClockSwitchController mKeyguardClockSwitchController;
@@ -216,6 +219,9 @@
     private NotificationsQuickSettingsContainer mNotificationContainerParent;
     @Mock
     private AmbientState mAmbientState;
+    @Mock
+    private UserManager mUserManager;
+
     private NotificationPanelViewController mNotificationPanelViewController;
     private View.AccessibilityDelegate mAccessibiltyDelegate;
 
@@ -299,11 +305,12 @@
                 mBiometricUnlockController, mStatusBarKeyguardViewManager,
                 mNotificationStackScrollLayoutController,
                 mKeyguardStatusViewComponentFactory,
+                mKeyguardUserSwitcherComponent,
                 mGroupManager,
                 mNotificationAreaController,
                 mAuthController,
-                new QSDetailDisplayer(),
                 mScrimController,
+                mUserManager,
                 mMediaDataManager,
                 mAmbientState,
                 mFeatureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
index fc1a791..e479882 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
@@ -60,9 +60,9 @@
     @Mock
     private lateinit var layoutInflater: LayoutInflater
     @Mock
-    private lateinit var keyguardUserSwitcher: KeyguardUserSwitcher
+    private lateinit var keyguardUserSwitcherController: KeyguardUserSwitcherController
 
-    private lateinit var adapter: KeyguardUserSwitcher.KeyguardUserAdapter
+    private lateinit var adapter: KeyguardUserSwitcherController.KeyguardUserAdapter
     private lateinit var picture: Bitmap
 
     @Before
@@ -72,8 +72,11 @@
         mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, layoutInflater)
         `when`(layoutInflater.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean()))
                 .thenReturn(inflatedUserDetailItemView)
-        adapter = KeyguardUserSwitcher.KeyguardUserAdapter(mContext, userSwitcherController,
-                keyguardUserSwitcher)
+        adapter = KeyguardUserSwitcherController.KeyguardUserAdapter(
+                mContext,
+                mContext.resources,
+                LayoutInflater.from(mContext),
+                userSwitcherController, keyguardUserSwitcherController)
         picture = UserIcons.convertToBitmap(mContext.getDrawable(R.drawable.ic_avatar_user))
     }
 
@@ -118,11 +121,11 @@
     }
 
     @Test
-    fun shouldRemoveOnClickListener_currentUser_notGuestUser_oldViewIsSameType() {
+    fun shouldSetOnOnClickListener_currentUser_notGuestUser_oldViewIsSameType() {
         val v: UserDetailItemView? = createViewFromSameType(
                 isCurrentUser = true, isGuestUser = false)
         assertNotNull(v)
-        verify(v)!!.setOnClickListener(null)
+        verify(v)!!.setOnClickListener(adapter)
     }
 
     @Test
@@ -150,11 +153,11 @@
     }
 
     @Test
-    fun shouldRemoveOnClickListener_currentUser_notGuestUser_oldViewIsDifferentType() {
+    fun shouldSetOnOnClickListener_currentUser_notGuestUser_oldViewIsDifferentType() {
         val v: UserDetailItemView? = createViewFromDifferentType(
                 isCurrentUser = true, isGuestUser = false)
         assertNotNull(v)
-        verify(v)!!.setOnClickListener(null)
+        verify(v)!!.setOnClickListener(adapter)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
index edaff5f..45828c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
@@ -32,13 +32,18 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayManager;
+import android.content.om.OverlayManagerTransaction;
 import android.os.UserHandle;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -70,11 +75,12 @@
     private static final String TEST_DISABLED_PREFIX = "com.example.";
     private static final String TEST_ENABLED_PREFIX = "com.example.enabled.";
 
-    private static final Map<String, String> ALL_CATEGORIES_MAP = Maps.newArrayMap();
+    private static final Map<String, OverlayIdentifier> ALL_CATEGORIES_MAP = Maps.newArrayMap();
 
     static {
         for (String category : THEME_CATEGORIES) {
-            ALL_CATEGORIES_MAP.put(category, TEST_DISABLED_PREFIX + category);
+            ALL_CATEGORIES_MAP.put(category,
+                    new OverlayIdentifier(TEST_DISABLED_PREFIX + category));
         }
     }
 
@@ -87,6 +93,8 @@
     OverlayManager mOverlayManager;
     @Mock
     DumpManager mDumpManager;
+    @Mock
+    OverlayManagerTransaction.Builder mTransactionBuilder;
 
     private ThemeOverlayApplier mManager;
 
@@ -94,7 +102,12 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
         mManager = new ThemeOverlayApplier(mOverlayManager, MoreExecutors.directExecutor(),
-                LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager);
+                LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager) {
+            @Override
+            protected OverlayManagerTransaction.Builder getTransactionBuilder() {
+                return mTransactionBuilder;
+            }
+        };
         when(mOverlayManager.getOverlayInfosForTarget(ANDROID_PACKAGE, UserHandle.SYSTEM))
                 .thenReturn(Lists.newArrayList(
                         createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_ACCENT_COLOR,
@@ -147,24 +160,26 @@
 
     @Test
     public void allCategoriesSpecified_allEnabledExclusively() {
-        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES);
+        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES);
+        verify(mOverlayManager).commit(any());
 
-        for (String overlayPackage : ALL_CATEGORIES_MAP.values()) {
-            verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER);
+        for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) {
+            verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
+                    eq(TEST_USER.getIdentifier()));
         }
     }
 
     @Test
     public void allCategoriesSpecified_sysuiCategoriesAlsoAppliedToSysuiUser() {
-        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES);
+        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES);
 
-        for (Map.Entry<String, String> entry : ALL_CATEGORIES_MAP.entrySet()) {
+        for (Map.Entry<String, OverlayIdentifier> entry : ALL_CATEGORIES_MAP.entrySet()) {
             if (SYSTEM_USER_CATEGORIES.contains(entry.getKey())) {
-                verify(mOverlayManager).setEnabledExclusiveInCategory(
-                        entry.getValue(), UserHandle.SYSTEM);
+                verify(mTransactionBuilder).setEnabled(eq(entry.getValue()), eq(true),
+                        eq(UserHandle.SYSTEM.getIdentifier()));
             } else {
-                verify(mOverlayManager, never()).setEnabledExclusiveInCategory(
-                        entry.getValue(), UserHandle.SYSTEM);
+                verify(mTransactionBuilder, never()).setEnabled(
+                        eq(entry.getValue()), eq(true), eq(UserHandle.SYSTEM.getIdentifier()));
             }
         }
     }
@@ -174,17 +189,34 @@
         Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES);
         UserHandle newUserHandle = UserHandle.of(10);
         userHandles.add(newUserHandle);
-        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, userHandles);
+        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, userHandles);
 
-        for (String overlayPackage : ALL_CATEGORIES_MAP.values()) {
-            verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER);
-            verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, newUserHandle);
+        for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) {
+            verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
+                    eq(TEST_USER.getIdentifier()));
+            verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
+                    eq(newUserHandle.getIdentifier()));
+        }
+    }
+
+    @Test
+    public void applyCurrentUserOverlays_createsPendingOverlays() {
+        Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES);
+        UserHandle newUserHandle = UserHandle.of(10);
+        userHandles.add(newUserHandle);
+        FabricatedOverlay[] pendingCreation = new FabricatedOverlay[] {
+                mock(FabricatedOverlay.class)
+        };
+        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, pendingCreation, userHandles);
+
+        for (FabricatedOverlay overlay : pendingCreation) {
+            verify(mTransactionBuilder).registerFabricatedOverlay(eq(overlay));
         }
     }
 
     @Test
     public void allCategoriesSpecified_overlayManagerNotQueried() {
-        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES);
+        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES);
 
         verify(mOverlayManager, never())
                 .getOverlayInfosForTarget(anyString(), any(UserHandle.class));
@@ -192,48 +224,56 @@
 
     @Test
     public void someCategoriesSpecified_specifiedEnabled_unspecifiedDisabled() {
-        Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
+        Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
         categoryToPackage.remove(OVERLAY_CATEGORY_ICON_SETTINGS);
         categoryToPackage.remove(OVERLAY_CATEGORY_ICON_ANDROID);
 
-        mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES);
+        mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES);
 
-        for (String overlayPackage : categoryToPackage.values()) {
-            verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER);
+        for (OverlayIdentifier overlayPackage : categoryToPackage.values()) {
+            verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
+                    eq(TEST_USER.getIdentifier()));
         }
-        verify(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_SETTINGS,
-                false, TEST_USER);
-        verify(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_ANDROID,
-                false, TEST_USER);
+        verify(mTransactionBuilder).setEnabled(
+                eq(new OverlayIdentifier(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_SETTINGS)),
+                eq(false), eq(TEST_USER.getIdentifier()));
+        verify(mTransactionBuilder).setEnabled(
+                eq(new OverlayIdentifier(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_ANDROID)),
+                eq(false), eq(TEST_USER.getIdentifier()));
     }
 
     @Test
     public void zeroCategoriesSpecified_allDisabled() {
-        mManager.applyCurrentUserOverlays(Maps.newArrayMap(), TEST_USER_HANDLES);
+        mManager.applyCurrentUserOverlays(Maps.newArrayMap(), null, TEST_USER_HANDLES);
 
         for (String category : THEME_CATEGORIES) {
-            verify(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + category, false, TEST_USER);
+            verify(mTransactionBuilder).setEnabled(
+                    eq(new OverlayIdentifier(TEST_ENABLED_PREFIX + category)), eq(false),
+                    eq(TEST_USER.getIdentifier()));
         }
     }
 
     @Test
     public void nonThemeCategorySpecified_ignored() {
-        Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
-        categoryToPackage.put("blah.category", "com.example.blah.category");
+        Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
+        categoryToPackage.put("blah.category", new OverlayIdentifier("com.example.blah.category"));
 
-        mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES);
+        mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES);
 
-        verify(mOverlayManager, never()).setEnabled("com.example.blah.category", false, TEST_USER);
-        verify(mOverlayManager, never()).setEnabledExclusiveInCategory("com.example.blah.category",
-                TEST_USER);
+        verify(mTransactionBuilder, never()).setEnabled(
+                eq(new OverlayIdentifier("com.example.blah.category")), eq(false),
+                eq(TEST_USER.getIdentifier()));
+        verify(mTransactionBuilder, never()).setEnabled(
+                eq(new OverlayIdentifier("com.example.blah.category")), eq(true),
+                eq(TEST_USER.getIdentifier()));
     }
 
     @Test
     public void overlayManagerOnlyQueriedForUnspecifiedPackages() {
-        Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
+        Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
         categoryToPackage.remove(OVERLAY_CATEGORY_ICON_SETTINGS);
 
-        mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES);
+        mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES);
 
         verify(mOverlayManager).getOverlayInfosForTarget(SETTINGS_PACKAGE, UserHandle.SYSTEM);
         verify(mOverlayManager, never()).getOverlayInfosForTarget(ANDROID_PACKAGE,
@@ -247,7 +287,8 @@
 
     private static OverlayInfo createOverlayInfo(String packageName, String targetPackageName,
             String category, boolean enabled) {
-        return new OverlayInfo(packageName, targetPackageName, null, category, "",
-                enabled ? OverlayInfo.STATE_ENABLED : OverlayInfo.STATE_DISABLED, 0, 0, false);
+        return new OverlayInfo(packageName, null, targetPackageName, null, category, "",
+                enabled ? OverlayInfo.STATE_ENABLED : OverlayInfo.STATE_DISABLED, 0, 0, false,
+                false);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index d33fac0..f7f8d03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.theme;
 
-import static com.android.systemui.theme.ThemeOverlayApplier.MONET_ACCENT_COLOR_PACKAGE;
-import static com.android.systemui.theme.ThemeOverlayApplier.MONET_SYSTEM_PALETTE_PACKAGE;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
 import static com.android.systemui.theme.ThemeOverlayController.USE_LOCK_SCREEN_WALLPAPER;
@@ -27,12 +25,15 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.WallpaperColors;
 import android.app.WallpaperManager;
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayIdentifier;
 import android.graphics.Color;
 import android.os.Handler;
 import android.os.UserHandle;
@@ -40,11 +41,13 @@
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 
+import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -56,7 +59,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Executor;
 
@@ -85,6 +87,8 @@
     private KeyguardStateController mKeyguardStateController;
     @Mock
     private DumpManager mDumpManager;
+    @Mock
+    private FeatureFlags mFeatureFlags;
     @Captor
     private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallback;
     @Captor
@@ -93,10 +97,20 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        when(mFeatureFlags.isMonetEnabled()).thenReturn(true);
         mThemeOverlayController = new ThemeOverlayController(null /* context */,
                 mBroadcastDispatcher, mBgHandler, mMainExecutor, mBgExecutor, mThemeOverlayApplier,
                 mSecureSettings, mWallpaperManager, mUserManager, mKeyguardStateController,
-                mDumpManager);
+                mDumpManager, mFeatureFlags) {
+            @Nullable
+            @Override
+            protected FabricatedOverlay getOverlay(int color, int type) {
+                FabricatedOverlay overlay = mock(FabricatedOverlay.class);
+                when(overlay.getIdentifier())
+                        .thenReturn(new OverlayIdentifier(Integer.toHexString(color | 0xff000000)));
+                return overlay;
+            }
+        };
 
         mThemeOverlayController.start();
         if (USE_LOCK_SCREEN_WALLPAPER) {
@@ -106,10 +120,6 @@
         verify(mWallpaperManager).addOnColorsChangedListener(mColorsListener.capture(), eq(null),
                 eq(UserHandle.USER_ALL));
         verify(mDumpManager).registerDumpable(any(), any());
-
-        List<Integer> colorList = List.of(Color.RED, Color.BLUE, 0x0CCCCC, 0x000CCC);
-        when(mThemeOverlayApplier.getAvailableAccentColors()).thenReturn(colorList);
-        when(mThemeOverlayApplier.getAvailableSystemColors()).thenReturn(colorList);
     }
 
     @Test
@@ -128,17 +138,17 @@
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
                 Color.valueOf(Color.BLUE), null);
         mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
-        ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
+        ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
+                ArgumentCaptor.forClass(Map.class);
 
-        verify(mThemeOverlayApplier).getAvailableSystemColors();
-        verify(mThemeOverlayApplier).getAvailableAccentColors();
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
+        verify(mThemeOverlayApplier)
+                .applyCurrentUserOverlays(themeOverlays.capture(), any(), any());
 
         // Assert that we received the colors that we were expecting
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
-                .isEqualTo(MONET_SYSTEM_PALETTE_PACKAGE + "FF0000");
+                .isEqualTo(new OverlayIdentifier("ffff0000"));
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR))
-                .isEqualTo(MONET_ACCENT_COLOR_PACKAGE + "0000FF");
+                .isEqualTo(new OverlayIdentifier("ff0000ff"));
 
         // Should not ask again if changed to same value
         mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
@@ -146,49 +156,6 @@
     }
 
     @Test
-    public void onWallpaperColorsChanged_whiteTheme() {
-        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.WHITE),
-                Color.valueOf(Color.BLUE), null);
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
-        ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
-
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
-
-        // Assert that we received the colors that we were expecting
-        assertThat(themeOverlays.getValue().containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE)).isFalse();
-    }
-
-    @Test
-    public void onWallpaperColorsChanged_blackTheme() {
-        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.BLACK),
-                Color.valueOf(Color.BLUE), null);
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
-        ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
-
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
-
-        // Assert that we received the colors that we were expecting
-        assertThat(themeOverlays.getValue().containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE)).isFalse();
-    }
-
-    @Test
-    public void onWallpaperColorsChanged_addsLeadingZerosToColors() {
-        // Should ask for a new theme when wallpaper colors change
-        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(0x0CCCCC),
-                Color.valueOf(0x000CCC), null);
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
-        ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
-
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
-
-        // Assert that we received the colors that we were expecting
-        assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
-                .isEqualTo(MONET_SYSTEM_PALETTE_PACKAGE + "0CCCCC");
-        assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR))
-                .isEqualTo(MONET_ACCENT_COLOR_PACKAGE + "000CCC");
-    }
-
-    @Test
     public void onWallpaperColorsChanged_preservesWallpaperPickerTheme() {
         // Should ask for a new theme when wallpaper colors change
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
@@ -201,14 +168,37 @@
                 .thenReturn(jsonString);
 
         mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
-        ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
+        ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
+                ArgumentCaptor.forClass(Map.class);
 
-        verify(mThemeOverlayApplier).getAvailableSystemColors();
-        verify(mThemeOverlayApplier).getAvailableAccentColors();
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
+        verify(mThemeOverlayApplier)
+                .applyCurrentUserOverlays(themeOverlays.capture(), any(), any());
 
         // Assert that we received the colors that we were expecting
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
-                .isEqualTo("override.package.name");
+                .isEqualTo(new OverlayIdentifier("override.package.name"));
+    }
+
+    @Test
+    public void onWallpaperColorsChanged_parsesColorsFromWallpaperPicker() {
+        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+                Color.valueOf(Color.BLUE), null);
+
+        String jsonString =
+                "{\"android.theme.customization.system_palette\":\"00FF00\"}";
+        when(mSecureSettings.getStringForUser(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+                .thenReturn(jsonString);
+
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
+                ArgumentCaptor.forClass(Map.class);
+
+        verify(mThemeOverlayApplier)
+                .applyCurrentUserOverlays(themeOverlays.capture(), any(), any());
+
+        // Assert that we received the colors that we were expecting
+        assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
+                .isEqualTo(new OverlayIdentifier("ff00ff00"));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
index c743fd0..365c62c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -57,8 +57,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.statusbar.FeatureFlags;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -88,7 +87,6 @@
     private static final String TEXT = "Hello World";
     private static final int MESSAGE_RES_ID = R.id.message;
 
-    private FakeExecutor mFakeDelayableExecutor = new FakeExecutor(new FakeSystemClock());
     private Context mContextSpy;
     private ToastUI mToastUI;
     @Mock private LayoutInflater mLayoutInflater;
@@ -99,6 +97,7 @@
     @Mock private PluginManager mPluginManager;
     @Mock private DumpManager mDumpManager;
     @Mock private ToastLogger mToastLogger;
+    @Mock private FeatureFlags mFeatureFlags;
 
     @Mock private ITransientNotificationCallback mCallback;
     @Captor private ArgumentCaptor<View> mViewCaptor;
@@ -107,12 +106,9 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-
-        // This is because inflate will result in WindowManager (WM) calls, which will fail since we
-        // are mocking it, so we mock LayoutInflater with the view obtained before mocking WM.
-        View view = ToastPresenter.getTextToastView(mContext, TEXT);
-        when(mLayoutInflater.inflate(eq(TEXT_TOAST_LAYOUT), any())).thenReturn(view);
-        mContext.addMockSystemService(LayoutInflater.class, mLayoutInflater);
+        when(mLayoutInflater.inflate(eq(TEXT_TOAST_LAYOUT), any())).thenReturn(
+                ToastPresenter.getTextToastView(mContext, TEXT));
+        when(mFeatureFlags.isToastStyleEnabled()).thenReturn(false);
 
         mContext.addMockSystemService(WindowManager.class, mWindowManager);
         mContextSpy = spy(mContext);
@@ -120,8 +116,8 @@
 
         doReturn(mContextSpy).when(mContextSpy).createContextAsUser(any(), anyInt());
         mToastUI = new ToastUI(mContextSpy, mCommandQueue, mNotificationManager,
-                mAccessibilityManager, new ToastFactory(mPluginManager, mDumpManager),
-                mFakeDelayableExecutor, mToastLogger);
+                mAccessibilityManager, new ToastFactory(mLayoutInflater, mPluginManager,
+                mDumpManager, mFeatureFlags), mToastLogger);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 76269dd..f1fc0b77 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -89,8 +89,6 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.bubbles.Bubble;
@@ -297,7 +295,7 @@
 
         mBubblesManager = new BubblesManager(
                 mContext,
-                mBubbleController.getImpl(),
+                mBubbleController.asBubbles(),
                 mNotificationShadeWindowController,
                 mStatusBarStateController,
                 mShadeController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index 5340ff7..9e10b21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -261,7 +261,7 @@
 
         mBubblesManager = new BubblesManager(
                 mContext,
-                mBubbleController.getImpl(),
+                mBubbleController.asBubbles(),
                 mNotificationShadeWindowController,
                 mStatusBarStateController,
                 mShadeController,
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 6c30999..38275f7 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -1245,10 +1245,9 @@
 
     @Override
     public IRestoreSession beginRestoreSessionForUser(
-            int userId, String packageName, String transportID,
-            @OperationType int operationType) throws RemoteException {
+            int userId, String packageName, String transportID) throws RemoteException {
         return isUserReadyForBackup(userId)
-                ? beginRestoreSession(userId, packageName, transportID, operationType) : null;
+                ? beginRestoreSession(userId, packageName, transportID) : null;
     }
 
     /**
@@ -1257,15 +1256,13 @@
      */
     @Nullable
     public IRestoreSession beginRestoreSession(
-            @UserIdInt int userId, String packageName, String transportName,
-            @OperationType int operationType) {
+            @UserIdInt int userId, String packageName, String transportName) {
         UserBackupManagerService userBackupManagerService =
                 getServiceForUserIfCallerHasPermission(userId, "beginRestoreSession()");
 
         return userBackupManagerService == null
                 ? null
-                : userBackupManagerService.beginRestoreSession(packageName, transportName,
-                        operationType);
+                : userBackupManagerService.beginRestoreSession(packageName, transportName);
     }
 
     @Override
@@ -1350,15 +1347,15 @@
         if (!isUserReadyForBackup(userId)) {
             return BackupManager.ERROR_BACKUP_NOT_ALLOWED;
         }
-        return requestBackup(userId, packages, observer, monitor, flags, OperationType.BACKUP);
+        return requestBackup(userId, packages, observer, monitor, flags);
     }
 
     @Override
     public int requestBackup(String[] packages, IBackupObserver observer,
-            IBackupManagerMonitor monitor, int flags, @OperationType int operationType)
+            IBackupManagerMonitor monitor, int flags)
             throws RemoteException {
         return requestBackup(binderGetCallingUserId(), packages,
-                observer, monitor, flags, operationType);
+                observer, monitor, flags);
     }
 
     /**
@@ -1370,15 +1367,13 @@
             String[] packages,
             IBackupObserver observer,
             IBackupManagerMonitor monitor,
-            int flags,
-            @OperationType int operationType) {
+            int flags) {
         UserBackupManagerService userBackupManagerService =
                 getServiceForUserIfCallerHasPermission(userId, "requestBackup()");
 
         return userBackupManagerService == null
                 ? BackupManager.ERROR_BACKUP_NOT_ALLOWED
-                : userBackupManagerService.requestBackup(packages, observer, monitor, flags,
-                        operationType);
+                : userBackupManagerService.requestBackup(packages, observer, monitor, flags);
     }
 
     @Override
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 136cd22f..faec95f 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -127,6 +127,7 @@
 import com.android.server.backup.restore.ActiveRestoreSession;
 import com.android.server.backup.restore.PerformUnifiedRestoreTask;
 import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportNotAvailableException;
 import com.android.server.backup.transport.TransportNotRegisteredException;
 import com.android.server.backup.utils.BackupEligibilityRules;
 import com.android.server.backup.utils.BackupManagerMonitorUtils;
@@ -1860,19 +1861,10 @@
 
     /**
      * Requests a backup for the inputted {@code packages} with a specified {@link
-     * IBackupManagerMonitor}.
-     */
-    public int requestBackup(String[] packages, IBackupObserver observer,
-            IBackupManagerMonitor monitor, int flags) {
-        return requestBackup(packages, observer, monitor, flags, OperationType.BACKUP);
-    }
-
-    /**
-     * Requests a backup for the inputted {@code packages} with a specified {@link
      * IBackupManagerMonitor} and {@link OperationType}.
      */
     public int requestBackup(String[] packages, IBackupObserver observer,
-            IBackupManagerMonitor monitor, int flags, @OperationType int operationType) {
+            IBackupManagerMonitor monitor, int flags) {
         mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "requestBackup");
 
         if (packages == null || packages.length < 1) {
@@ -1903,13 +1895,16 @@
 
         final TransportClient transportClient;
         final String transportDirName;
+        int operationType;
         try {
             transportDirName =
                     mTransportManager.getTransportDirName(
                             mTransportManager.getCurrentTransportName());
             transportClient =
                     mTransportManager.getCurrentTransportClientOrThrow("BMS.requestBackup()");
-        } catch (TransportNotRegisteredException e) {
+            operationType = getOperationTypeFromTransport(transportClient);
+        } catch (TransportNotRegisteredException | TransportNotAvailableException
+                | RemoteException e) {
             BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
             monitor = BackupManagerMonitorUtils.monitorEvent(monitor,
                     BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL,
@@ -4024,15 +4019,13 @@
     }
 
     /** Hand off a restore session. */
-    public IRestoreSession beginRestoreSession(String packageName, String transport,
-            @OperationType int operationType) {
+    public IRestoreSession beginRestoreSession(String packageName, String transport) {
         if (DEBUG) {
             Slog.v(
                     TAG,
                     addUserIdToLogMessage(
                             mUserId,
-                            "beginRestoreSession: pkg=" + packageName + " transport=" + transport
-                                + "operationType=" + operationType));
+                            "beginRestoreSession: pkg=" + packageName + " transport=" + transport));
         }
 
         boolean needPermission = true;
@@ -4073,6 +4066,17 @@
             }
         }
 
+        int operationType;
+        try {
+            operationType = getOperationTypeFromTransport(
+                    mTransportManager.getTransportClientOrThrow(transport, /* caller */
+                            "BMS.beginRestoreSession"));
+        } catch (TransportNotAvailableException | TransportNotRegisteredException
+                | RemoteException e) {
+            Slog.w(TAG, "Failed to get operation type from transport: " + e);
+            return null;
+        }
+
         synchronized (this) {
             if (mActiveRestoreSession != null) {
                 Slog.i(
@@ -4356,6 +4360,23 @@
         }
     }
 
+    @VisibleForTesting
+    @OperationType int getOperationTypeFromTransport(TransportClient transportClient)
+            throws TransportNotAvailableException, RemoteException {
+        long oldCallingId = Binder.clearCallingIdentity();
+        try {
+            IBackupTransport transport = transportClient.connectOrThrow(
+                    /* caller */ "BMS.getOperationTypeFromTransport");
+            if ((transport.getTransportFlags() & BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER) != 0) {
+                return OperationType.MIGRATION;
+            } else {
+                return OperationType.BACKUP;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(oldCallingId);
+        }
+    }
+
     private static String addUserIdToLogMessage(int userId, String message) {
         return "[UserID:" + userId + "] " + message;
     }
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index a6cfae4..c6a8660 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -2,7 +2,7 @@
 per-file ConnectivityService.java,ConnectivityServiceInitializer.java,NetworkManagementService.java,NsdService.java = file:/services/core/java/com/android/server/net/OWNERS
 
 # Vibrator / Threads
-per-file VibratorManagerService.java, VibratorService.java, DisplayThread.java = michaelwr@google.com, ogunwale@google.com
+per-file VibratorManagerService.java, DisplayThread.java = michaelwr@google.com, ogunwale@google.com
 
 # Zram writeback
 per-file ZramWriteback.java = minchan@google.com, rajekumar@google.com
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 00d8b0f..d10cf4d 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -30,6 +30,7 @@
 import android.os.UserManager;
 import android.service.persistentdata.IPersistentDataBlockService;
 import android.service.persistentdata.PersistentDataBlockManager;
+import android.text.TextUtils;
 import android.util.Slog;
 
 import com.android.internal.R;
@@ -147,14 +148,15 @@
     private int getAllowedUid(int userHandle) {
         String allowedPackage = mContext.getResources()
                 .getString(R.string.config_persistentDataPackageName);
-        PackageManager pm = mContext.getPackageManager();
         int allowedUid = -1;
-        try {
-            allowedUid = pm.getPackageUidAsUser(allowedPackage,
-                    PackageManager.MATCH_SYSTEM_ONLY, userHandle);
-        } catch (PackageManager.NameNotFoundException e) {
-            // not expected
-            Slog.e(TAG, "not able to find package " + allowedPackage, e);
+        if (!TextUtils.isEmpty(allowedPackage)) {
+            try {
+                allowedUid = mContext.getPackageManager().getPackageUidAsUser(
+                        allowedPackage, PackageManager.MATCH_SYSTEM_ONLY, userHandle);
+            } catch (PackageManager.NameNotFoundException e) {
+                // not expected
+                Slog.e(TAG, "not able to find package " + allowedPackage, e);
+            }
         }
         return allowedUid;
     }
diff --git a/services/core/java/com/android/server/VibratorManagerService.java b/services/core/java/com/android/server/VibratorManagerService.java
index e7e5d67..d264f85 100644
--- a/services/core/java/com/android/server/VibratorManagerService.java
+++ b/services/core/java/com/android/server/VibratorManagerService.java
@@ -18,9 +18,14 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.app.AppOpsManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.hardware.vibrator.IVibrator;
 import android.os.BatteryStats;
 import android.os.Binder;
@@ -74,6 +79,7 @@
 /** System implementation of {@link IVibratorManagerService}. */
 public class VibratorManagerService extends IVibratorManagerService.Stub {
     private static final String TAG = "VibratorManagerService";
+    private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
     private static final boolean DEBUG = false;
     private static final VibrationAttributes DEFAULT_ATTRIBUTES =
             new VibrationAttributes.Builder().build();
@@ -89,7 +95,7 @@
         @Override
         public void onStart() {
             mService = new VibratorManagerService(getContext(), new Injector());
-            publishBinderService("vibrator_manager", mService);
+            publishBinderService(Context.VIBRATOR_MANAGER_SERVICE, mService);
         }
 
         @Override
@@ -105,6 +111,7 @@
 
     private final Object mLock = new Object();
     private final Context mContext;
+    private final String mSystemUiPackage;
     private final PowerManager.WakeLock mWakeLock;
     private final IBatteryStats mBatteryStatsService;
     private final Handler mHandler;
@@ -128,6 +135,26 @@
     private VibrationScaler mVibrationScaler;
     private InputDeviceDelegate mInputDeviceDelegate;
 
+    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+                synchronized (mLock) {
+                    // When the system is entering a non-interactive state, we want
+                    // to cancel vibrations in case a misbehaving app has abandoned
+                    // them.  However it may happen that the system is currently playing
+                    // haptic feedback as part of the transition.  So we don't cancel
+                    // system vibrations.
+                    if (mCurrentVibration != null
+                            && !isSystemHapticFeedback(mCurrentVibration.getVibration())) {
+                        mNextVibration = null;
+                        mCurrentVibration.cancel();
+                    }
+                }
+            }
+        }
+    };
+
     static native long nativeInit(OnSyncedVibrationCompleteListener listener);
 
     static native long nativeGetFinalizer();
@@ -155,6 +182,9 @@
                 com.android.internal.R.integer.config_previousVibrationsDumpLimit);
         mVibratorManagerRecords = new VibratorManagerRecords(dumpLimit);
 
+        mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
+                .getSystemUiServiceComponent().getPackageName();
+
         mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
                 BatteryStats.SERVICE_NAME));
 
@@ -184,6 +214,12 @@
         for (int i = 0; i < mVibrators.size(); i++) {
             mVibrators.valueAt(i).off();
         }
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_SCREEN_OFF);
+        context.registerReceiver(mIntentReceiver, filter);
+
+        injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
     }
 
     /** Finish initialization at boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}. */
@@ -371,7 +407,7 @@
                         mVibratorManagerRecords.record(mCurrentExternalVibration);
                         mCurrentExternalVibration.externalVibration.mute();
                         mCurrentExternalVibration = null;
-                        // TODO(b/167946816): set external control to false
+                        setExternalControl(false);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -432,6 +468,12 @@
         }
     }
 
+    private void setExternalControl(boolean externalControl) {
+        for (int i = 0; i < mVibrators.size(); i++) {
+            mVibrators.valueAt(i).setExternalControl(externalControl);
+        }
+    }
+
     @GuardedBy("mLock")
     private void updateAlwaysOnLocked(AlwaysOnVibration vib) {
         for (int i = 0; i < vib.effects.size(); i++) {
@@ -507,6 +549,12 @@
     }
 
     @GuardedBy("mLock")
+    private void endVibrationLocked(ExternalVibrationHolder vib, Vibration.Status status) {
+        vib.end(status);
+        mVibratorManagerRecords.record(vib);
+    }
+
+    @GuardedBy("mLock")
     private void reportFinishedVibrationLocked(Vibration.Status status) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
         Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
@@ -827,6 +875,13 @@
                 == PackageManager.PERMISSION_GRANTED;
     }
 
+    private boolean isSystemHapticFeedback(Vibration vib) {
+        if (vib.attrs.getUsage() != VibrationAttributes.USAGE_TOUCH) {
+            return false;
+        }
+        return vib.uid == Process.SYSTEM_UID || vib.uid == 0 || mSystemUiPackage.equals(vib.opPkg);
+    }
+
     @GuardedBy("mLock")
     private void onAllVibratorsLocked(Consumer<VibratorController> consumer) {
         for (int i = 0; i < mVibrators.size(); i++) {
@@ -859,6 +914,10 @@
                 VibratorController.OnVibrationCompleteListener listener) {
             return new VibratorController(vibratorId, listener);
         }
+
+        void addService(String name, IBinder service) {
+            ServiceManager.addService(name, service);
+        }
     }
 
     /**
@@ -1088,14 +1147,14 @@
                 }
                 pw.println();
                 pw.println("  mCurrentVibration:");
-                pw.println("    " + mCurrentVibration == null
-                        ? null : mCurrentVibration.getVibration().getDebugInfo());
+                pw.println("    " + (mCurrentVibration == null
+                        ? null : mCurrentVibration.getVibration().getDebugInfo()));
                 pw.println("  mNextVibration:");
-                pw.println("    " + mNextVibration == null
-                        ? null : mNextVibration.getVibration().getDebugInfo());
+                pw.println("    " + (mNextVibration == null
+                        ? null : mNextVibration.getVibration().getDebugInfo()));
                 pw.println("  mCurrentExternalVibration:");
-                pw.println("    " + mCurrentExternalVibration == null
-                        ? null : mCurrentExternalVibration.getDebugInfo());
+                pw.println("    " + (mCurrentExternalVibration == null
+                        ? null : mCurrentExternalVibration.getDebugInfo()));
                 pw.println();
                 pw.println("  mVibrationSettings=" + mVibrationSettings);
                 for (int i = 0; i < mPreviousVibrations.size(); i++) {
@@ -1168,6 +1227,145 @@
         }
     }
 
+    /** Implementation of {@link IExternalVibratorService} to be triggered on external control. */
+    private final class ExternalVibratorService extends IExternalVibratorService.Stub {
+        ExternalVibrationDeathRecipient mCurrentExternalDeathRecipient;
+
+        @Override
+        public int onExternalVibrationStart(ExternalVibration vib) {
+            if (!hasExternalControlCapability()) {
+                return IExternalVibratorService.SCALE_MUTE;
+            }
+            if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE,
+                    vib.getUid(), -1 /*owningUid*/, true /*exported*/)
+                    != PackageManager.PERMISSION_GRANTED) {
+                Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
+                        + " tried to play externally controlled vibration"
+                        + " without VIBRATE permission, ignoring.");
+                return IExternalVibratorService.SCALE_MUTE;
+            }
+
+            int mode = checkAppOpModeLocked(vib.getUid(), vib.getPackage(),
+                    vib.getVibrationAttributes());
+            if (mode != AppOpsManager.MODE_ALLOWED) {
+                ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
+                vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
+                if (mode == AppOpsManager.MODE_ERRORED) {
+                    Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid());
+                    endVibrationLocked(vibHolder, Vibration.Status.IGNORED_ERROR_APP_OPS);
+                } else {
+                    endVibrationLocked(vibHolder, Vibration.Status.IGNORED_APP_OPS);
+                }
+                return vibHolder.scale;
+            }
+
+            VibrationThread cancelingVibration = null;
+            int scale;
+            synchronized (mLock) {
+                if (mCurrentExternalVibration != null
+                        && mCurrentExternalVibration.externalVibration.equals(vib)) {
+                    // We are already playing this external vibration, so we can return the same
+                    // scale calculated in the previous call to this method.
+                    return mCurrentExternalVibration.scale;
+                }
+                if (mCurrentExternalVibration == null) {
+                    // If we're not under external control right now, then cancel any normal
+                    // vibration that may be playing and ready the vibrator for external control.
+                    if (mCurrentVibration != null) {
+                        mNextVibration = null;
+                        mCurrentVibration.cancel();
+                        cancelingVibration = mCurrentVibration;
+                    }
+                } else {
+                    endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED);
+                }
+                // At this point we either have an externally controlled vibration playing, or
+                // no vibration playing. Since the interface defines that only one externally
+                // controlled vibration can play at a time, by returning something other than
+                // SCALE_MUTE from this function we can be assured that if we are currently
+                // playing vibration, it will be muted in favor of the new vibration.
+                //
+                // Note that this doesn't support multiple concurrent external controls, as we
+                // would need to mute the old one still if it came from a different controller.
+                mCurrentExternalVibration = new ExternalVibrationHolder(vib);
+                mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient();
+                vib.linkToDeath(mCurrentExternalDeathRecipient);
+                mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale(
+                        vib.getVibrationAttributes().getUsage());
+                scale = mCurrentExternalVibration.scale;
+            }
+
+            if (cancelingVibration != null) {
+                try {
+                    cancelingVibration.join();
+                } catch (InterruptedException e) {
+                    Slog.w("Interrupted while waiting for vibration to finish before starting "
+                                    + "external control", e);
+                }
+            }
+            if (DEBUG) {
+                Slog.d(TAG, "Vibrator going under external control.");
+            }
+            setExternalControl(true);
+            if (DEBUG) {
+                Slog.e(TAG, "Playing external vibration: " + vib);
+            }
+            return scale;
+        }
+
+        @Override
+        public void onExternalVibrationStop(ExternalVibration vib) {
+            synchronized (mLock) {
+                if (mCurrentExternalVibration != null
+                        && mCurrentExternalVibration.externalVibration.equals(vib)) {
+                    if (DEBUG) {
+                        Slog.e(TAG, "Stopping external vibration" + vib);
+                    }
+                    stopExternalVibrateLocked(Vibration.Status.FINISHED);
+                }
+            }
+        }
+
+        private void stopExternalVibrateLocked(Vibration.Status status) {
+            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "stopExternalVibrateLocked");
+            try {
+                if (mCurrentExternalVibration == null) {
+                    return;
+                }
+                endVibrationLocked(mCurrentExternalVibration, status);
+                mCurrentExternalVibration.externalVibration.unlinkToDeath(
+                        mCurrentExternalDeathRecipient);
+                mCurrentExternalDeathRecipient = null;
+                mCurrentExternalVibration = null;
+                setExternalControl(false);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+            }
+        }
+
+        private boolean hasExternalControlCapability() {
+            for (int i = 0; i < mVibrators.size(); i++) {
+                if (mVibrators.valueAt(i).hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private class ExternalVibrationDeathRecipient implements IBinder.DeathRecipient {
+            public void binderDied() {
+                synchronized (mLock) {
+                    if (mCurrentExternalVibration != null) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "External vibration finished because binder died");
+                        }
+                        stopExternalVibrateLocked(Vibration.Status.CANCELLED);
+                    }
+                }
+            }
+        }
+    }
+
     /** Provide limited functionality from {@link VibratorManagerService} as shell commands. */
     private final class VibratorManagerShellCommand extends ShellCommand {
         public static final String SHELL_PACKAGE_NAME = "com.android.shell";
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
deleted file mode 100644
index 2ac365d..0000000
--- a/services/core/java/com/android/server/VibratorService.java
+++ /dev/null
@@ -1,1243 +0,0 @@
-/*
- * Copyright (C) 2008 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;
-
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.AppOpsManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
-import android.hardware.vibrator.IVibrator;
-import android.os.BatteryStats;
-import android.os.Binder;
-import android.os.CombinedVibrationEffect;
-import android.os.ExternalVibration;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IExternalVibratorService;
-import android.os.IVibratorService;
-import android.os.IVibratorStateListener;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.ResultReceiver;
-import android.os.ServiceManager;
-import android.os.ShellCallback;
-import android.os.ShellCommand;
-import android.os.Trace;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.os.VibratorInfo;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.util.DumpUtils;
-import com.android.server.vibrator.InputDeviceDelegate;
-import com.android.server.vibrator.Vibration;
-import com.android.server.vibrator.VibrationScaler;
-import com.android.server.vibrator.VibrationSettings;
-import com.android.server.vibrator.VibrationThread;
-import com.android.server.vibrator.VibratorController;
-import com.android.server.vibrator.VibratorController.OnVibrationCompleteListener;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/** System implementation of {@link IVibratorService}. */
-public class VibratorService extends IVibratorService.Stub {
-    private static final String TAG = "VibratorService";
-    private static final boolean DEBUG = false;
-    private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
-
-    // Default vibration attributes. Used when vibration is requested without attributes
-    private static final VibrationAttributes DEFAULT_ATTRIBUTES =
-            new VibrationAttributes.Builder().build();
-
-    // Used to generate globally unique vibration ids.
-    private final AtomicInteger mNextVibrationId = new AtomicInteger(1); // 0 = no callback
-
-    private final LinkedList<Vibration.DebugInfo> mPreviousRingVibrations;
-    private final LinkedList<Vibration.DebugInfo> mPreviousNotificationVibrations;
-    private final LinkedList<Vibration.DebugInfo> mPreviousAlarmVibrations;
-    private final LinkedList<Vibration.DebugInfo> mPreviousExternalVibrations;
-    private final LinkedList<Vibration.DebugInfo> mPreviousVibrations;
-    private final int mPreviousVibrationsLimit;
-    private final Handler mH;
-    private final Object mLock = new Object();
-    private final VibratorController mVibratorController;
-    private final VibrationCallbacks mVibrationCallbacks = new VibrationCallbacks();
-
-    private final Context mContext;
-    private final PowerManager.WakeLock mWakeLock;
-    private final AppOpsManager mAppOps;
-    private final IBatteryStats mBatteryStatsService;
-    private final String mSystemUiPackage;
-    private VibrationSettings mVibrationSettings;
-    private VibrationScaler mVibrationScaler;
-    private InputDeviceDelegate mInputDeviceDelegate;
-
-    @GuardedBy("mLock")
-    private VibrationThread mThread;
-    @GuardedBy("mLock")
-    private VibrationThread mNextVibrationThread;
-
-    @GuardedBy("mLock")
-    private Vibration mCurrentVibration;
-    private int mCurVibUid = -1;
-    private ExternalVibrationHolder mCurrentExternalVibration;
-
-    /**
-     * Implementation of {@link VibrationThread.VibrationCallbacks} that reports finished
-     * vibrations.
-     */
-    private final class VibrationCallbacks implements VibrationThread.VibrationCallbacks {
-
-        @Override
-        public boolean prepareSyncedVibration(long requiredCapabilities, int[] vibratorIds) {
-            return false;
-        }
-
-        @Override
-        public boolean triggerSyncedVibration(long vibrationId) {
-            return false;
-        }
-
-        @Override
-        public void cancelSyncedVibration() {
-        }
-
-        @Override
-        public void onVibrationEnded(long vibrationId, Vibration.Status status) {
-            if (DEBUG) {
-                Slog.d(TAG, "Vibration thread finished with status " + status);
-            }
-            synchronized (mLock) {
-                if (mCurrentVibration != null && mCurrentVibration.id == vibrationId) {
-                    mThread = null;
-                    reportFinishVibrationLocked(status);
-                    if (mNextVibrationThread != null) {
-                        startVibrationThreadLocked(mNextVibrationThread);
-                        mNextVibrationThread = null;
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Implementation of {@link OnVibrationCompleteListener} with a weak reference to this service.
-     */
-    private static final class VibrationCompleteListener implements OnVibrationCompleteListener {
-        private WeakReference<VibratorService> mServiceRef;
-
-        VibrationCompleteListener(VibratorService service) {
-            mServiceRef = new WeakReference<>(service);
-        }
-
-        @Override
-        public void onComplete(int vibratorId, long vibrationId) {
-            VibratorService service = mServiceRef.get();
-            if (service != null) {
-                service.onVibrationComplete(vibratorId, vibrationId);
-            }
-        }
-    }
-
-    /** Holder for a {@link ExternalVibration}. */
-    private final class ExternalVibrationHolder {
-
-        public final ExternalVibration externalVibration;
-        public int scale;
-
-        private final long mStartTimeDebug;
-        private long mEndTimeDebug;
-        private Vibration.Status mStatus;
-
-        private ExternalVibrationHolder(ExternalVibration externalVibration) {
-            this.externalVibration = externalVibration;
-            this.scale = IExternalVibratorService.SCALE_NONE;
-            mStartTimeDebug = System.currentTimeMillis();
-            mStatus = Vibration.Status.RUNNING;
-        }
-
-        public void end(Vibration.Status status) {
-            if (mStatus != Vibration.Status.RUNNING) {
-                // Vibration already ended, keep first ending status set and ignore this one.
-                return;
-            }
-            mStatus = status;
-            mEndTimeDebug = System.currentTimeMillis();
-        }
-
-        public Vibration.DebugInfo getDebugInfo() {
-            return new Vibration.DebugInfo(
-                    mStartTimeDebug, mEndTimeDebug, /* effect= */ null, /* originalEffect= */ null,
-                    scale, externalVibration.getVibrationAttributes(),
-                    externalVibration.getUid(), externalVibration.getPackage(),
-                    /* reason= */ null, mStatus);
-        }
-    }
-
-    VibratorService(Context context) {
-        this(context, new Injector());
-    }
-
-    @VisibleForTesting
-    VibratorService(Context context, Injector injector) {
-        mH = injector.createHandler(Looper.myLooper());
-        mVibratorController = injector.createVibratorController(
-                new VibrationCompleteListener(this));
-
-        // Reset the hardware to a default state, in case this is a runtime
-        // restart instead of a fresh boot.
-        mVibratorController.off();
-
-        mContext = context;
-        PowerManager pm = context.getSystemService(PowerManager.class);
-        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
-        mWakeLock.setReferenceCounted(true);
-
-        mAppOps = mContext.getSystemService(AppOpsManager.class);
-        mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
-                BatteryStats.SERVICE_NAME));
-        mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
-                .getSystemUiServiceComponent().getPackageName();
-
-        mPreviousVibrationsLimit = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_previousVibrationsDumpLimit);
-
-        mPreviousRingVibrations = new LinkedList<>();
-        mPreviousNotificationVibrations = new LinkedList<>();
-        mPreviousAlarmVibrations = new LinkedList<>();
-        mPreviousVibrations = new LinkedList<>();
-        mPreviousExternalVibrations = new LinkedList<>();
-
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_SCREEN_OFF);
-        context.registerReceiver(mIntentReceiver, filter);
-
-        injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
-    }
-
-    public void systemReady() {
-        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorService#systemReady");
-        try {
-            mVibrationSettings = new VibrationSettings(mContext, mH);
-            mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
-            mInputDeviceDelegate = new InputDeviceDelegate(mContext, mH);
-
-            mVibrationSettings.addListener(this::updateVibrators);
-
-            mContext.registerReceiver(new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    mVibrationSettings.updateSettings();
-                }
-            }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH);
-
-            updateVibrators();
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-        }
-    }
-
-    /** Callback for when vibration is complete, to be called by native. */
-    @VisibleForTesting
-    public void onVibrationComplete(int vibratorId, long vibrationId) {
-        synchronized (mLock) {
-            if (mCurrentVibration != null && mCurrentVibration.id == vibrationId
-                    && mThread != null) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Vibration onComplete callback, notifying VibrationThread");
-                }
-                // Let the thread playing the vibration handle the callback, since it might be
-                // expecting the vibrator to turn off multiple times during a single vibration.
-                mThread.vibratorComplete(vibratorId);
-            }
-        }
-    }
-
-    @Override // Binder call
-    public boolean hasVibrator() {
-        // For now, we choose to ignore the presence of input devices that have vibrators
-        // when reporting whether the device has a vibrator.  Applications often use this
-        // information to decide whether to enable certain features so they expect the
-        // result of hasVibrator() to be constant.  For now, just report whether
-        // the device has a built-in vibrator.
-        return mVibratorController.isAvailable();
-    }
-
-    @Override // Binder call
-    public boolean isVibrating() {
-        if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) {
-            throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission");
-        }
-        return mVibratorController.isVibrating();
-    }
-
-    @Override // Binder call
-    public VibratorInfo getVibratorInfo() {
-        return mVibratorController.getVibratorInfo();
-    }
-
-    @Override // Binder call
-    public boolean registerVibratorStateListener(IVibratorStateListener listener) {
-        if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) {
-            throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission");
-        }
-        return mVibratorController.registerVibratorStateListener(listener);
-    }
-
-    @Override // Binder call
-    @GuardedBy("mLock")
-    public boolean unregisterVibratorStateListener(IVibratorStateListener listener) {
-        if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) {
-            throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission");
-        }
-        return mVibratorController.unregisterVibratorStateListener(listener);
-    }
-
-    @Override // Binder call
-    public boolean hasAmplitudeControl() {
-        // Input device vibrators always support amplitude controls.
-        return mInputDeviceDelegate.isAvailable()
-                || mVibratorController.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL);
-    }
-
-    private void verifyIncomingUid(int uid) {
-        if (uid == Binder.getCallingUid()) {
-            return;
-        }
-        if (Binder.getCallingPid() == Process.myPid()) {
-            return;
-        }
-        mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
-                Binder.getCallingPid(), Binder.getCallingUid(), null);
-    }
-
-    /**
-     * Validate the incoming VibrationEffect.
-     *
-     * We can't throw exceptions here since we might be called from some system_server component,
-     * which would bring the whole system down.
-     *
-     * @return whether the VibrationEffect is valid
-     */
-    private static boolean verifyVibrationEffect(VibrationEffect effect) {
-        if (effect == null) {
-            // Effect must not be null.
-            Slog.wtf(TAG, "effect must not be null");
-            return false;
-        }
-        try {
-            effect.validate();
-        } catch (Exception e) {
-            Slog.wtf(TAG, "Encountered issue when verifying VibrationEffect.", e);
-            return false;
-        }
-        return true;
-    }
-
-    private VibrationEffect fixupVibrationEffect(VibrationEffect effect) {
-        if (effect instanceof VibrationEffect.Prebaked
-                && ((VibrationEffect.Prebaked) effect).shouldFallback()) {
-            VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
-            VibrationEffect fallback = mVibrationSettings.getFallbackEffect(prebaked.getId());
-            return new VibrationEffect.Prebaked(prebaked.getId(), prebaked.getEffectStrength(),
-                    fallback);
-        }
-        return effect;
-    }
-
-    private VibrationAttributes fixupVibrationAttributes(VibrationAttributes attrs) {
-        if (attrs == null) {
-            attrs = DEFAULT_ATTRIBUTES;
-        }
-        if (shouldBypassDnd(attrs)) {
-            if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-                    || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-                    || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
-                final int flags = attrs.getFlags()
-                        & ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
-                attrs = new VibrationAttributes.Builder(attrs)
-                                .setFlags(flags, attrs.getFlags()).build();
-            }
-        }
-
-        return attrs;
-    }
-
-    @Override // Binder call
-    public void vibrate(int uid, String opPkg, VibrationEffect effect,
-            @Nullable VibrationAttributes attrs, String reason, IBinder token) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
-        try {
-            if (!hasPermission(android.Manifest.permission.VIBRATE)) {
-                throw new SecurityException("Requires VIBRATE permission");
-            }
-            if (token == null) {
-                Slog.e(TAG, "token must not be null");
-                return;
-            }
-            verifyIncomingUid(uid);
-            if (!verifyVibrationEffect(effect)) {
-                return;
-            }
-            effect = fixupVibrationEffect(effect);
-            attrs = fixupVibrationAttributes(attrs);
-            Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(),
-                    CombinedVibrationEffect.createSynced(effect), attrs, uid, opPkg, reason);
-
-            // If our current vibration is longer than the new vibration and is the same amplitude,
-            // then just let the current one finish.
-            synchronized (mLock) {
-                VibrationEffect currentEffect =
-                        mCurrentVibration == null ? null : getEffect(mCurrentVibration);
-                if (effect instanceof VibrationEffect.OneShot
-                        && currentEffect instanceof VibrationEffect.OneShot) {
-                    VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect;
-                    VibrationEffect.OneShot currentOneShot =
-                            (VibrationEffect.OneShot) currentEffect;
-                    if (currentOneShot.getDuration() > newOneShot.getDuration()
-                            && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
-                        if (DEBUG) {
-                            Slog.d(TAG,
-                                    "Ignoring incoming vibration in favor of current vibration");
-                        }
-                        endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_ONGOING);
-                        return;
-                    }
-                }
-
-
-                // If something has external control of the vibrator, assume that it's more
-                // important for now.
-                if (mCurrentExternalVibration != null) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
-                    }
-                    endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_EXTERNAL);
-                    return;
-                }
-
-                // If the current vibration is repeating and the incoming one is non-repeating,
-                // then ignore the non-repeating vibration. This is so that we don't cancel
-                // vibrations that are meant to grab the attention of the user, like ringtones and
-                // alarms, in favor of one-shot vibrations that are likely quite short.
-                if (!isRepeatingVibration(effect)
-                        && mCurrentVibration != null
-                        && isRepeatingVibration(currentEffect)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
-                    }
-                    endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_ALARM);
-                    return;
-                }
-
-                if (!mVibrationSettings.shouldVibrateForUid(uid, vib.attrs.getUsage())) {
-                    Slog.e(TAG, "Ignoring incoming vibration as process with"
-                            + " uid= " + uid + " is background,"
-                            + " attrs= " + vib.attrs);
-                    endVibrationLocked(vib, Vibration.Status.IGNORED_BACKGROUND);
-                    return;
-                }
-                final long ident = Binder.clearCallingIdentity();
-                try {
-                    doCancelVibrateLocked(Vibration.Status.CANCELLED);
-                    startVibrationLocked(vib);
-                    boolean isNextVibration = mNextVibrationThread != null
-                            && vib.equals(mNextVibrationThread.getVibration());
-
-                    if (!vib.hasEnded() && !vib.equals(mCurrentVibration) && !isNextVibration) {
-                        // Vibration was unexpectedly ignored: add to list for debugging
-                        endVibrationLocked(vib, Vibration.Status.IGNORED);
-                    }
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
-                }
-            }
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-        }
-    }
-
-    private boolean hasPermission(String permission) {
-        return mContext.checkCallingOrSelfPermission(permission)
-                == PackageManager.PERMISSION_GRANTED;
-    }
-
-    private static boolean isRepeatingVibration(VibrationEffect effect) {
-        return effect.getDuration() == Long.MAX_VALUE;
-    }
-
-    private static <T extends VibrationEffect> T getEffect(Vibration vib) {
-        return (T) ((CombinedVibrationEffect.Mono) vib.getEffect()).getEffect();
-    }
-
-    private void endVibrationLocked(Vibration vib, Vibration.Status status) {
-        final LinkedList<Vibration.DebugInfo> previousVibrations;
-        switch (vib.attrs.getUsage()) {
-            case VibrationAttributes.USAGE_NOTIFICATION:
-                previousVibrations = mPreviousNotificationVibrations;
-                break;
-            case VibrationAttributes.USAGE_RINGTONE:
-                previousVibrations = mPreviousRingVibrations;
-                break;
-            case VibrationAttributes.USAGE_ALARM:
-                previousVibrations = mPreviousAlarmVibrations;
-                break;
-            default:
-                previousVibrations = mPreviousVibrations;
-        }
-        if (previousVibrations.size() > mPreviousVibrationsLimit) {
-            previousVibrations.removeFirst();
-        }
-        vib.end(status);
-        previousVibrations.addLast(vib.getDebugInfo());
-    }
-
-    private void endVibrationLocked(ExternalVibrationHolder vib, Vibration.Status status) {
-        if (mPreviousExternalVibrations.size() > mPreviousVibrationsLimit) {
-            mPreviousExternalVibrations.removeFirst();
-        }
-        vib.end(status);
-        mPreviousExternalVibrations.addLast(vib.getDebugInfo());
-    }
-
-    @Override // Binder call
-    public void cancelVibrate(IBinder token) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.VIBRATE,
-                "cancelVibrate");
-
-        synchronized (mLock) {
-            if (mCurrentVibration != null && mCurrentVibration.token == token) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Canceling vibration.");
-                }
-                final long ident = Binder.clearCallingIdentity();
-                try {
-                    mNextVibrationThread = null;
-                    doCancelVibrateLocked(Vibration.Status.CANCELLED);
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
-                }
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void doCancelVibrateLocked(Vibration.Status status) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelVibrateLocked");
-        try {
-            if (mThread != null) {
-                mThread.cancel();
-            }
-            mInputDeviceDelegate.cancelVibrateIfAvailable();
-            if (mCurrentExternalVibration != null) {
-                endVibrationLocked(mCurrentExternalVibration, status);
-                mCurrentExternalVibration.externalVibration.mute();
-                mCurrentExternalVibration = null;
-                mVibratorController.setExternalControl(false);
-            }
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void startVibrationLocked(final Vibration vib) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
-        try {
-            if (!shouldVibrate(vib)) {
-                return;
-            }
-            applyVibrationIntensityScalingLocked(vib);
-            startVibrationInnerLocked(vib);
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void startVibrationInnerLocked(Vibration vib) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationInnerLocked");
-        try {
-            boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
-                    vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs);
-            if (inputDevicesAvailable) {
-                endVibrationLocked(vib, Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
-            } else if (mThread == null) {
-                startVibrationThreadLocked(new VibrationThread(vib, mVibratorController, mWakeLock,
-                        mBatteryStatsService, mVibrationCallbacks));
-            } else {
-                mNextVibrationThread = new VibrationThread(vib, mVibratorController, mWakeLock,
-                        mBatteryStatsService, mVibrationCallbacks);
-            }
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void startVibrationThreadLocked(VibrationThread thread) {
-        Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
-        mCurrentVibration = thread.getVibration();
-        mThread = thread;
-        mThread.start();
-    }
-
-    /** Scale the vibration effect by the intensity as appropriate based its intent. */
-    private void applyVibrationIntensityScalingLocked(Vibration vib) {
-        vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage()));
-    }
-
-    private static boolean shouldBypassDnd(VibrationAttributes attrs) {
-        return attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY);
-    }
-
-    private int getAppOpMode(int uid, String packageName, VibrationAttributes attrs) {
-        int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE,
-                attrs.getAudioUsage(), uid, packageName);
-        if (mode == AppOpsManager.MODE_ALLOWED) {
-            mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, uid, packageName);
-        }
-
-        if (mode == AppOpsManager.MODE_IGNORED && shouldBypassDnd(attrs)) {
-            // If we're just ignoring the vibration op then this is set by DND and we should ignore
-            // if we're asked to bypass. AppOps won't be able to record this operation, so make
-            // sure we at least note it in the logs for debugging.
-            Slog.d(TAG, "Bypassing DND for vibrate from uid " + uid);
-            mode = AppOpsManager.MODE_ALLOWED;
-        }
-        return mode;
-    }
-
-    private boolean shouldVibrate(Vibration vib) {
-        if (!mVibrationSettings.shouldVibrateForPowerMode(vib.attrs.getUsage())) {
-            endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_POWER);
-            return false;
-        }
-
-        int intensity = mVibrationSettings.getCurrentIntensity(vib.attrs.getUsage());
-        if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
-            endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_SETTINGS);
-            return false;
-        }
-
-        if (!mVibrationSettings.shouldVibrateForRingerMode(vib.attrs.getUsage())) {
-            if (DEBUG) {
-                Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
-            }
-            endVibrationLocked(vib, Vibration.Status.IGNORED_RINGTONE);
-            return false;
-        }
-
-        final int mode = getAppOpMode(vib.uid, vib.opPkg, vib.attrs);
-        if (mode != AppOpsManager.MODE_ALLOWED) {
-            if (mode == AppOpsManager.MODE_ERRORED) {
-                // We might be getting calls from within system_server, so we don't actually
-                // want to throw a SecurityException here.
-                Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid);
-                endVibrationLocked(vib, Vibration.Status.IGNORED_ERROR_APP_OPS);
-            } else {
-                endVibrationLocked(vib, Vibration.Status.IGNORED_APP_OPS);
-            }
-            return false;
-        }
-
-        return true;
-    }
-
-    @GuardedBy("mLock")
-    private void reportFinishVibrationLocked(Vibration.Status status) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
-        try {
-            if (mCurrentVibration != null) {
-                endVibrationLocked(mCurrentVibration, status);
-                mAppOps.finishOp(AppOpsManager.OP_VIBRATE, mCurrentVibration.uid,
-                        mCurrentVibration.opPkg);
-
-                Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
-                mCurrentVibration = null;
-            }
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-        }
-    }
-
-    @VisibleForTesting
-    void updateVibrators() {
-        synchronized (mLock) {
-            boolean inputDevicesChanged = mInputDeviceDelegate.updateInputDeviceVibrators(
-                    mVibrationSettings.shouldVibrateInputDevices());
-
-            if (mCurrentVibration == null) {
-                return;
-            }
-
-            if (inputDevicesChanged || !mVibrationSettings.shouldVibrateForPowerMode(
-                    mCurrentVibration.attrs.getUsage())) {
-                // If the state changes out from under us then just reset.
-                doCancelVibrateLocked(Vibration.Status.CANCELLED);
-            }
-        }
-    }
-
-    private boolean isSystemHapticFeedback(Vibration vib) {
-        if (vib.attrs.getUsage() != VibrationAttributes.USAGE_TOUCH) {
-            return false;
-        }
-        return vib.uid == Process.SYSTEM_UID || vib.uid == 0 || mSystemUiPackage.equals(vib.opPkg);
-    }
-
-    private void dumpInternal(PrintWriter pw) {
-        pw.println("Vibrator Service:");
-        synchronized (mLock) {
-            pw.print("  mCurrentVibration=");
-            if (mCurrentVibration != null) {
-                pw.println(mCurrentVibration.getDebugInfo().toString());
-            } else {
-                pw.println("null");
-            }
-            pw.print("  mCurrentExternalVibration=");
-            if (mCurrentExternalVibration != null) {
-                pw.println(mCurrentExternalVibration.getDebugInfo().toString());
-            } else {
-                pw.println("null");
-            }
-            pw.println("  mVibratorController=" + mVibratorController);
-            pw.println("  mVibrationSettings=" + mVibrationSettings);
-            pw.println();
-            pw.println("  Previous ring vibrations:");
-            for (Vibration.DebugInfo info : mPreviousRingVibrations) {
-                pw.print("    ");
-                pw.println(info.toString());
-            }
-
-            pw.println("  Previous notification vibrations:");
-            for (Vibration.DebugInfo info : mPreviousNotificationVibrations) {
-                pw.println("    " + info);
-            }
-
-            pw.println("  Previous alarm vibrations:");
-            for (Vibration.DebugInfo info : mPreviousAlarmVibrations) {
-                pw.println("    " + info);
-            }
-
-            pw.println("  Previous vibrations:");
-            for (Vibration.DebugInfo info : mPreviousVibrations) {
-                pw.println("    " + info);
-            }
-
-            pw.println("  Previous external vibrations:");
-            for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
-                pw.println("    " + info);
-            }
-        }
-    }
-
-    private void dumpProto(FileDescriptor fd) {
-        final ProtoOutputStream proto = new ProtoOutputStream(fd);
-
-        synchronized (mLock) {
-            if (mCurrentVibration != null) {
-                mCurrentVibration.getDebugInfo().dumpProto(proto,
-                        VibratorServiceDumpProto.CURRENT_VIBRATION);
-            }
-            if (mCurrentExternalVibration != null) {
-                mCurrentExternalVibration.getDebugInfo().dumpProto(proto,
-                        VibratorServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
-            }
-            proto.write(VibratorServiceDumpProto.IS_VIBRATING, mVibratorController.isVibrating());
-            proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
-                    mVibratorController.isUnderExternalControl());
-            mVibrationSettings.dumpProto(proto);
-
-            for (Vibration.DebugInfo info : mPreviousRingVibrations) {
-                info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS);
-            }
-
-            for (Vibration.DebugInfo info : mPreviousNotificationVibrations) {
-                info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS);
-            }
-
-            for (Vibration.DebugInfo info : mPreviousAlarmVibrations) {
-                info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS);
-            }
-
-            for (Vibration.DebugInfo info : mPreviousVibrations) {
-                info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_VIBRATIONS);
-            }
-
-            for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
-                info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
-            }
-        }
-        proto.flush();
-    }
-
-    /** Point of injection for test dependencies */
-    @VisibleForTesting
-    static class Injector {
-
-        VibratorController createVibratorController(OnVibrationCompleteListener listener) {
-            return new VibratorController(/* vibratorId= */ -1, listener);
-        }
-
-        Handler createHandler(Looper looper) {
-            return new Handler(looper);
-        }
-
-        void addService(String name, IBinder service) {
-            ServiceManager.addService(name, service);
-        }
-    }
-
-    BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
-                synchronized (mLock) {
-                    // When the system is entering a non-interactive state, we want
-                    // to cancel vibrations in case a misbehaving app has abandoned
-                    // them.  However it may happen that the system is currently playing
-                    // haptic feedback as part of the transition.  So we don't cancel
-                    // system vibrations.
-                    if (mCurrentVibration != null && !isSystemHapticFeedback(mCurrentVibration)) {
-                        mNextVibrationThread = null;
-                        doCancelVibrateLocked(Vibration.Status.CANCELLED);
-                    }
-                }
-            }
-        }
-    };
-
-    @Override
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
-
-        final long ident = Binder.clearCallingIdentity();
-
-        boolean isDumpProto = false;
-        for (String arg : args) {
-            if (arg.equals("--proto")) {
-                isDumpProto = true;
-            }
-        }
-        try {
-            if (isDumpProto) {
-                dumpProto(fd);
-            } else {
-                dumpInternal(pw);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    @Override
-    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
-            String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
-        new VibratorShellCommand(this).exec(this, in, out, err, args, callback, resultReceiver);
-    }
-
-    final class ExternalVibratorService extends IExternalVibratorService.Stub {
-        ExternalVibrationDeathRecipient mCurrentExternalDeathRecipient;
-
-        @Override
-        public int onExternalVibrationStart(ExternalVibration vib) {
-            if (!mVibratorController.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
-                return IExternalVibratorService.SCALE_MUTE;
-            }
-            if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE,
-                    vib.getUid(), -1 /*owningUid*/, true /*exported*/)
-                    != PackageManager.PERMISSION_GRANTED) {
-                Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
-                        + " tried to play externally controlled vibration"
-                        + " without VIBRATE permission, ignoring.");
-                return IExternalVibratorService.SCALE_MUTE;
-            }
-
-            int mode = getAppOpMode(vib.getUid(), vib.getPackage(), vib.getVibrationAttributes());
-            if (mode != AppOpsManager.MODE_ALLOWED) {
-                ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
-                vibHolder.scale = SCALE_MUTE;
-                if (mode == AppOpsManager.MODE_ERRORED) {
-                    Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid());
-                    endVibrationLocked(vibHolder, Vibration.Status.IGNORED_ERROR_APP_OPS);
-                } else {
-                    endVibrationLocked(vibHolder, Vibration.Status.IGNORED_APP_OPS);
-                }
-                return IExternalVibratorService.SCALE_MUTE;
-            }
-
-            VibrationThread cancelingVibration = null;
-            int scale;
-            synchronized (mLock) {
-                if (mCurrentExternalVibration != null
-                        && mCurrentExternalVibration.externalVibration.equals(vib)) {
-                    // We are already playing this external vibration, so we can return the same
-                    // scale calculated in the previous call to this method.
-                    return mCurrentExternalVibration.scale;
-                }
-                if (mCurrentExternalVibration == null) {
-                    // If we're not under external control right now, then cancel any normal
-                    // vibration that may be playing and ready the vibrator for external control.
-                    mNextVibrationThread = null;
-                    doCancelVibrateLocked(Vibration.Status.CANCELLED);
-                    cancelingVibration = mThread;
-                } else {
-                    endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED);
-                }
-                // At this point we either have an externally controlled vibration playing, or
-                // no vibration playing. Since the interface defines that only one externally
-                // controlled vibration can play at a time, by returning something other than
-                // SCALE_MUTE from this function we can be assured that if we are currently
-                // playing vibration, it will be muted in favor of the new vibration.
-                //
-                // Note that this doesn't support multiple concurrent external controls, as we
-                // would need to mute the old one still if it came from a different controller.
-                mCurrentExternalVibration = new ExternalVibrationHolder(vib);
-                mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient();
-                vib.linkToDeath(mCurrentExternalDeathRecipient);
-                mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale(
-                        vib.getVibrationAttributes().getUsage());
-                scale = mCurrentExternalVibration.scale;
-            }
-            if (cancelingVibration != null) {
-                try {
-                    cancelingVibration.join();
-                } catch (InterruptedException e) {
-                    Slog.w("Interrupted while waiting current vibration to be cancelled before "
-                            + "starting external vibration", e);
-                }
-            }
-            if (DEBUG) {
-                Slog.d(TAG, "Vibrator going under external control.");
-            }
-            mVibratorController.setExternalControl(true);
-            if (DEBUG) {
-                Slog.e(TAG, "Playing external vibration: " + vib);
-            }
-            return scale;
-        }
-
-        @Override
-        public void onExternalVibrationStop(ExternalVibration vib) {
-            synchronized (mLock) {
-                if (mCurrentExternalVibration != null
-                        && mCurrentExternalVibration.externalVibration.equals(vib)) {
-                    if (DEBUG) {
-                        Slog.e(TAG, "Stopping external vibration" + vib);
-                    }
-                    doCancelExternalVibrateLocked(Vibration.Status.FINISHED);
-                }
-            }
-        }
-
-        private void doCancelExternalVibrateLocked(Vibration.Status status) {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelExternalVibrateLocked");
-            try {
-                if (mCurrentExternalVibration == null) {
-                    return;
-                }
-                endVibrationLocked(mCurrentExternalVibration, status);
-                mCurrentExternalVibration.externalVibration.unlinkToDeath(
-                        mCurrentExternalDeathRecipient);
-                mCurrentExternalDeathRecipient = null;
-                mCurrentExternalVibration = null;
-                mVibratorController.setExternalControl(false);
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-
-        private class ExternalVibrationDeathRecipient implements IBinder.DeathRecipient {
-            public void binderDied() {
-                synchronized (mLock) {
-                    if (mCurrentExternalVibration != null) {
-                        if (DEBUG) {
-                            Slog.d(TAG, "External vibration finished because binder died");
-                        }
-                        doCancelExternalVibrateLocked(Vibration.Status.CANCELLED);
-                    }
-                }
-            }
-        }
-    }
-
-    private final class VibratorShellCommand extends ShellCommand {
-
-        private final IBinder mToken;
-
-        private final class CommonOptions {
-            public boolean force = false;
-            public void check(String opt) {
-                switch (opt) {
-                    case "-f":
-                        force = true;
-                        break;
-                }
-            }
-        }
-
-        private VibratorShellCommand(IBinder token) {
-            mToken = token;
-        }
-
-        @Override
-        public int onCommand(String cmd) {
-            if ("vibrate".equals(cmd)) {
-                return runVibrate();
-            } else if ("waveform".equals(cmd)) {
-                return runWaveform();
-            } else if ("prebaked".equals(cmd)) {
-                return runPrebaked();
-            } else if ("capabilities".equals(cmd)) {
-                return runCapabilities();
-            } else if ("cancel".equals(cmd)) {
-                cancelVibrate(mToken);
-                return 0;
-            }
-            return handleDefaultCommands(cmd);
-        }
-
-        private boolean checkDoNotDisturb(CommonOptions opts) {
-            if (mVibrationSettings.isInZenMode() && !opts.force) {
-                try (PrintWriter pw = getOutPrintWriter();) {
-                    pw.print("Ignoring because device is on DND mode ");
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        private int runVibrate() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runVibrate");
-            try {
-                CommonOptions commonOptions = new CommonOptions();
-
-                String opt;
-                while ((opt = getNextOption()) != null) {
-                    commonOptions.check(opt);
-                }
-
-                if (checkDoNotDisturb(commonOptions)) {
-                    return 0;
-                }
-
-                final long duration = Long.parseLong(getNextArgRequired());
-                String description = getNextArg();
-                if (description == null) {
-                    description = "Shell command";
-                }
-
-                VibrationEffect effect =
-                        VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE);
-                VibrationAttributes attrs = createVibrationAttributes(commonOptions);
-                vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
-                        mToken);
-                return 0;
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-
-        private int runWaveform() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runWaveform");
-            try {
-                String description = "Shell command";
-                int repeat = -1;
-                ArrayList<Integer> amplitudesList = null;
-                CommonOptions commonOptions = new CommonOptions();
-
-                String opt;
-                while ((opt = getNextOption()) != null) {
-                    switch (opt) {
-                        case "-d":
-                            description = getNextArgRequired();
-                            break;
-                        case "-r":
-                            repeat = Integer.parseInt(getNextArgRequired());
-                            break;
-                        case "-a":
-                            if (amplitudesList == null) {
-                                amplitudesList = new ArrayList<Integer>();
-                            }
-                            break;
-                        default:
-                            commonOptions.check(opt);
-                            break;
-                    }
-                }
-
-                if (checkDoNotDisturb(commonOptions)) {
-                    return 0;
-                }
-
-                ArrayList<Long> timingsList = new ArrayList<Long>();
-
-                String arg;
-                while ((arg = getNextArg()) != null) {
-                    if (amplitudesList != null && amplitudesList.size() < timingsList.size()) {
-                        amplitudesList.add(Integer.parseInt(arg));
-                    } else {
-                        timingsList.add(Long.parseLong(arg));
-                    }
-                }
-
-                VibrationEffect effect;
-                long[] timings = timingsList.stream().mapToLong(Long::longValue).toArray();
-                if (amplitudesList == null) {
-                    effect = VibrationEffect.createWaveform(timings, repeat);
-                } else {
-                    int[] amplitudes =
-                            amplitudesList.stream().mapToInt(Integer::intValue).toArray();
-                    effect = VibrationEffect.createWaveform(timings, amplitudes, repeat);
-                }
-                VibrationAttributes attrs = createVibrationAttributes(commonOptions);
-                vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
-                        mToken);
-                return 0;
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-
-        private int runPrebaked() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runPrebaked");
-            try {
-                CommonOptions commonOptions = new CommonOptions();
-                boolean shouldFallback = false;
-
-                String opt;
-                while ((opt = getNextOption()) != null) {
-                    if ("-b".equals(opt)) {
-                        shouldFallback = true;
-                    } else {
-                        commonOptions.check(opt);
-                    }
-                }
-
-                if (checkDoNotDisturb(commonOptions)) {
-                    return 0;
-                }
-
-                final int id = Integer.parseInt(getNextArgRequired());
-
-                String description = getNextArg();
-                if (description == null) {
-                    description = "Shell command";
-                }
-
-                VibrationEffect effect = VibrationEffect.get(id, shouldFallback);
-                VibrationAttributes attrs = createVibrationAttributes(commonOptions);
-                vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
-                        mToken);
-                return 0;
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-
-        private int runCapabilities() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runCapabilities");
-            try (PrintWriter pw = getOutPrintWriter();) {
-                pw.println("Vibrator capabilities:");
-                if (mVibratorController.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
-                    pw.println("  Always on effects");
-                }
-                if (mVibratorController.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
-                    pw.println("  Compose effects");
-                }
-                if (mVibratorController.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
-                    pw.println("  Amplitude control");
-                }
-                if (mVibratorController.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
-                    pw.println("  External control");
-                }
-                if (mVibratorController.hasCapability(IVibrator.CAP_EXTERNAL_AMPLITUDE_CONTROL)) {
-                    pw.println("  External amplitude control");
-                }
-                pw.println("");
-                return 0;
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-
-        private VibrationAttributes createVibrationAttributes(CommonOptions commonOptions) {
-            final int flags = commonOptions.force
-                    ? VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY
-                    : 0;
-            return new VibrationAttributes.Builder()
-                    .setFlags(flags, VibrationAttributes.FLAG_ALL_SUPPORTED)
-                    // Used to apply Settings.System.HAPTIC_FEEDBACK_INTENSITY to scale effects.
-                    .setUsage(VibrationAttributes.USAGE_TOUCH)
-                    .build();
-        }
-
-        @Override
-        public void onHelp() {
-            try (PrintWriter pw = getOutPrintWriter();) {
-                pw.println("Vibrator commands:");
-                pw.println("  help");
-                pw.println("    Prints this help text.");
-                pw.println("");
-                pw.println("  vibrate duration [description]");
-                pw.println("    Vibrates for duration milliseconds; ignored when device is on ");
-                pw.println("    DND (Do Not Disturb) mode; touch feedback strength user setting ");
-                pw.println("    will be used to scale amplitude.");
-                pw.println("  waveform [-d description] [-r index] [-a] duration [amplitude] ...");
-                pw.println("    Vibrates for durations and amplitudes in list; ignored when ");
-                pw.println("    device is on DND (Do Not Disturb) mode; touch feedback strength ");
-                pw.println("    user setting will be used to scale amplitude.");
-                pw.println("    If -r is provided, the waveform loops back to the specified");
-                pw.println("    index (e.g. 0 loops from the beginning)");
-                pw.println("    If -a is provided, the command accepts duration-amplitude pairs;");
-                pw.println("    otherwise, it accepts durations only and alternates off/on");
-                pw.println("    Duration is in milliseconds; amplitude is a scale of 1-255.");
-                pw.println("  prebaked [-b] effect-id [description]");
-                pw.println("    Vibrates with prebaked effect; ignored when device is on DND ");
-                pw.println("    (Do Not Disturb) mode; touch feedback strength user setting ");
-                pw.println("    will be used to scale amplitude.");
-                pw.println("    If -b is provided, the prebaked fallback effect will be played if");
-                pw.println("    the device doesn't support the given effect-id.");
-                pw.println("  capabilities");
-                pw.println("    Prints capabilities of this device.");
-                pw.println("  cancel");
-                pw.println("    Cancels any active vibration");
-                pw.println("Common Options:");
-                pw.println("  -f - Force. Ignore Do Not Disturb setting.");
-                pw.println("");
-            }
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index c6947c2d..b994389 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -15,8 +15,6 @@
  */
 package com.android.server.am;
 
-import static com.android.internal.power.MeasuredEnergyArray.SUBSYSTEM_DISPLAY;
-
 import android.annotation.Nullable;
 import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.bluetooth.BluetoothAdapter;
@@ -39,13 +37,12 @@
 import android.telephony.TelephonyManager;
 import android.util.IntArray;
 import android.util.Slog;
-import android.util.SparseIntArray;
+import android.util.SparseArray;
 import android.util.SparseLongArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BatteryStatsImpl;
-import com.android.internal.power.MeasuredEnergyArray;
 import com.android.internal.power.MeasuredEnergyStats;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.function.pooled.PooledLambda;
@@ -148,13 +145,13 @@
     private WifiActivityEnergyInfo mLastWifiInfo =
             new WifiActivityEnergyInfo(0, 0, 0, 0, 0, 0);
 
-    /** Maps the EnergyConsumer id to it's corresponding {@link MeasuredEnergySubsystem} */
+    /**
+     * Maps an {@link EnergyConsumerType} to it's corresponding {@link EnergyConsumer#id}s,
+     * unless it is of {@link EnergyConsumer#type}=={@link EnergyConsumerType#OTHER}
+     */
+    // TODO: Hook this up (it isn't used yet)
     @GuardedBy("mWorkerLock")
-    private @Nullable SparseIntArray mEnergyConsumerToSubsystemMap = null;
-
-    /** Maps a {@link MeasuredEnergySubsystem} to it's corresponding EnergyConsumer id */
-    @GuardedBy("mWorkerLock")
-    private @Nullable SparseIntArray mSubsystemToEnergyConsumerMap = null;
+    private @Nullable SparseArray<int[]> mEnergyConsumerTypeToIdMap = null;
 
     /** Snapshot of measured energies, or null if no measured energies are supported. */
     @GuardedBy("mWorkerLock")
@@ -204,18 +201,26 @@
             mWifiManager = wm;
             mTelephony = tm;
             mPowerStatsInternal = psi;
+
+            boolean[] supportedStdBuckets = null;
+            int numCustomBuckets = 0;
             if (mPowerStatsInternal != null) {
-                populateEnergyConsumerSubsystemMapsLocked();
-                final MeasuredEnergyArray initialMeasuredEnergies = getEnergyConsumptionData();
-                mMeasuredEnergySnapshot = initialMeasuredEnergies == null
-                        ? null : new MeasuredEnergySnapshot(initialMeasuredEnergies);
-                final boolean[] supportedStdBuckets
-                        = getSupportedEnergyBuckets(initialMeasuredEnergies);
-                final int numCustomBuckets = 0; // TODO: Get this from initialMeasuredEnergies
-                synchronized (mStats) {
-                    mStats.initMeasuredEnergyStatsLocked(supportedStdBuckets, numCustomBuckets);
+                final SparseArray<EnergyConsumer> idToConsumer
+                        = populateEnergyConsumerSubsystemMapsLocked();
+                if (idToConsumer != null) {
+                    mMeasuredEnergySnapshot = new MeasuredEnergySnapshot(idToConsumer);
+                    final EnergyConsumerResult[] initialEcrs = getEnergyConsumptionData();
+                    // According to spec, initialEcrs will include 0s for consumers that haven't
+                    // used any energy yet, as long as they are supported; however, attributed uid
+                    // energies will be absent if their energy is 0.
+                    mMeasuredEnergySnapshot.updateAndGetDelta(initialEcrs);
+                    numCustomBuckets = mMeasuredEnergySnapshot.getNumOtherOrdinals();
+                    supportedStdBuckets = getSupportedEnergyBuckets(idToConsumer);
                 }
             }
+            synchronized (mStats) {
+                mStats.initMeasuredEnergyStatsLocked(supportedStdBuckets, numCustomBuckets);
+            }
         }
     }
 
@@ -568,7 +573,9 @@
         } catch (ExecutionException e) {
             Slog.w(TAG, "exception reading modem stats: " + e.getCause());
         }
-        final SparseLongArray energyDeltas = mMeasuredEnergySnapshot == null ? null :
+
+        final MeasuredEnergySnapshot.MeasuredEnergyDeltaData measuredEnergyDeltas =
+                mMeasuredEnergySnapshot == null ? null :
                 mMeasuredEnergySnapshot.updateAndGetDelta(getMeasuredEnergyLocked(updateFlags));
 
         final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -576,6 +583,7 @@
         final long elapsedRealtimeUs = elapsedRealtime * 1000;
         final long uptimeUs = uptime * 1000;
 
+        // Now that we have finally received all the data, we can tell mStats about it.
         synchronized (mStats) {
             mStats.addHistoryEventLocked(
                     elapsedRealtime,
@@ -601,10 +609,21 @@
             }
 
             // Inform mStats about each applicable measured energy.
-            if (energyDeltas != null) {
-                final long displayEnergy = energyDeltas.get(SUBSYSTEM_DISPLAY, 0L);
-                // Always pass in what BatteryExternalStatsWorker thinks screenState is.
-                mStats.updateDisplayEnergyLocked(displayEnergy, screenState, elapsedRealtime);
+            if (measuredEnergyDeltas != null) {
+                final long displayEnergy = measuredEnergyDeltas.displayEnergyUJ;
+                if (displayEnergy != MeasuredEnergySnapshot.UNAVAILABLE) {
+                    // If updating, pass in what BatteryExternalStatsWorker thinks screenState is.
+                    mStats.updateDisplayEnergyLocked(displayEnergy, screenState, elapsedRealtime);
+                }
+            }
+            // Inform mStats about each applicable custom energy bucket.
+            if (measuredEnergyDeltas != null && measuredEnergyDeltas.otherTotalEnergyUJ != null) {
+                // Iterate over the custom (EnergyConsumerType.OTHER) ordinals.
+                for (int ord = 0; ord < measuredEnergyDeltas.otherTotalEnergyUJ.length; ord++) {
+                    long totalEnergy = measuredEnergyDeltas.otherTotalEnergyUJ[ord];
+                    SparseLongArray uidEnergies = measuredEnergyDeltas.otherUidEnergiesUJ[ord];
+                    mStats.updateCustomMeasuredEnergyDataLocked(ord, totalEnergy, uidEnergies);
+                }
             }
 
             if (bluetoothInfo != null) {
@@ -621,7 +640,8 @@
 
         if (wifiInfo != null) {
             if (wifiInfo.isValid()) {
-                // TODO: wifiEnergyDelta = energyDeltas.get(MeasuredEnergyArray.SUBSYSTEM_WIFI, 0L);
+                // TODO: wifiEnergyDelta = measuredEnergyDeltas.consumerTypeEnergyUJ
+                //               .get(EnergyConsumerType.WIFI, MeasuredEnergySnapshot.UNAVAILABLE)
                 mStats.updateWifiState(extractDeltaLocked(wifiInfo)
                         /*, TODO: wifiEnergyDelta */, elapsedRealtime, uptime);
             } else {
@@ -740,21 +760,23 @@
     }
 
     /**
-     * Map the {@link MeasuredEnergyArray.MeasuredEnergySubsystem}s in the given energyArray to
+     * Map the {@link EnergyConsumerType}s in the given energyArray to
      * their corresponding {@link MeasuredEnergyStats.StandardEnergyBucket}s.
      * Does not include custom energy buckets (which are always, by definition, supported).
      *
      * @return array with true for index i if standard energy bucket i is supported.
      */
-    private static @Nullable boolean[] getSupportedEnergyBuckets(MeasuredEnergyArray energyArray) {
-        if (energyArray == null) {
+    private static @Nullable boolean[] getSupportedEnergyBuckets(
+            SparseArray<EnergyConsumer> idToConsumer) {
+        if (idToConsumer == null) {
             return null;
         }
         final boolean[] buckets = new boolean[MeasuredEnergyStats.NUMBER_STANDARD_ENERGY_BUCKETS];
-        final int size = energyArray.size();
-        for (int energyIdx = 0; energyIdx < size; energyIdx++) {
-            switch (energyArray.getSubsystem(energyIdx)) {
-                case MeasuredEnergyArray.SUBSYSTEM_DISPLAY:
+        final int size = idToConsumer.size();
+        for (int idx = 0; idx < size; idx++) {
+            final EnergyConsumer consumer = idToConsumer.valueAt(idx);
+            switch (consumer.type) {
+                case EnergyConsumerType.DISPLAY:
                     buckets[MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON] = true;
                     buckets[MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE] = true;
                     buckets[MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_OTHER] = true;
@@ -764,71 +786,22 @@
         return buckets;
     }
 
-    /**
-     * Get a {@link MeasuredEnergyArray} with the latest
-     * {@link MeasuredEnergyArray.MeasuredEnergySubsystem} energy usage since boot.
-     *
-     * TODO(b/176988041): Replace {@link MeasuredEnergyArray} usage with {@link
-     * EnergyConsumerResult}[]
-     */
+    /** Get {@link EnergyConsumerResult}s with the latest energy usage since boot. */
     @GuardedBy("mWorkerLock")
-    @VisibleForTesting
-    public @Nullable MeasuredEnergyArray getEnergyConsumptionData() {
-        final EnergyConsumerResult[] results;
+    private @Nullable EnergyConsumerResult[] getEnergyConsumptionData() {
         try {
-            results = mPowerStatsInternal.getEnergyConsumedAsync(new int[0])
+            return mPowerStatsInternal.getEnergyConsumedAsync(new int[0])
                     .get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
         } catch (Exception e) {
             Slog.e(TAG, "Failed to getEnergyConsumedAsync", e);
             return null;
         }
-        if (results == null) return null;
-        final int size = results.length;
-        final int[] subsystems = new int[size];
-        final long[] energyUJ = new long[size];
-
-        int count = 0;
-        for (int i = 0; i < size; i++) {
-            final EnergyConsumerResult consumer = results[i];
-            final int subsystem = mEnergyConsumerToSubsystemMap.get(consumer.id,
-                    MeasuredEnergyArray.SUBSYSTEM_UNKNOWN);
-            if (subsystem == MeasuredEnergyArray.SUBSYSTEM_UNKNOWN) continue;
-            subsystems[count] = subsystem;
-            energyUJ[count] = consumer.energyUWs;
-            count++;
-        }
-        final int arraySize = count;
-        return new MeasuredEnergyArray() {
-            @Override
-            public int getSubsystem(int index) {
-                if (index >= size()) {
-                    throw new IllegalArgumentException(
-                            "Out of bounds subsystem index! index : " + index + ", size : "
-                                    + size());
-                }
-                return subsystems[index];
-            }
-
-            @Override
-            public long getEnergy(int index) {
-                if (index >= size()) {
-                    throw new IllegalArgumentException(
-                            "Out of bounds subsystem index! index : " + index + ", size : "
-                                    + size());
-                }
-                return energyUJ[index];
-            }
-
-            @Override
-            public int size() {
-                return arraySize;
-            }
-        };
     }
 
-    /** Fetch MeasuredEnergyArray for supported subsystems based on the given updateFlags. */
+    /** Fetch EnergyConsumerResult[] for supported subsystems based on the given updateFlags. */
     @GuardedBy("mWorkerLock")
-    private @Nullable MeasuredEnergyArray getMeasuredEnergyLocked(@ExternalUpdateFlag int flags) {
+    private @Nullable EnergyConsumerResult[] getMeasuredEnergyLocked(@ExternalUpdateFlag int flags)
+    {
         if (mMeasuredEnergySnapshot == null || mPowerStatsInternal == null) return null;
 
         if (flags == UPDATE_ALL) {
@@ -836,12 +809,13 @@
             return getEnergyConsumptionData();
         }
 
-        final List<Integer> energySubsystems = new ArrayList<>();
+        final List<Integer> energyConsumerIds = new ArrayList<>();
         if ((flags & UPDATE_DISPLAY) != 0) {
-            addEnergyConsumerIdLocked(energySubsystems, SUBSYSTEM_DISPLAY);
+            addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.DISPLAY);
         }
         // TODO: Wifi, Bluetooth, etc., go here
-        if (energySubsystems.isEmpty()) {
+
+        if (energyConsumerIds.isEmpty()) {
             return null;
         }
         // TODO: Query *specific* subsystems from HAL based on energyConsumerIds.toArray()
@@ -849,59 +823,48 @@
     }
 
     @GuardedBy("mWorkerLock")
-    private void addEnergyConsumerIdLocked(List<Integer> energyConsumerIds,
-            @MeasuredEnergyArray.MeasuredEnergySubsystem int consumerId) {
-        if (mMeasuredEnergySnapshot.hasSubsystem(consumerId)) {
-            energyConsumerIds.add(consumerId);
-        }
+    private void addEnergyConsumerIdLocked(
+            List<Integer> energyConsumerIds, @EnergyConsumerType int type) {
+        final int consumerId = 0; // TODO: Use mEnergyConsumerTypeToIdMap to get this
+        energyConsumerIds.add(consumerId);
     }
 
+    /** Populates the cached type->ids map, and returns the (inverse) id->EnergyConsumer map. */
     @GuardedBy("mWorkerLock")
-    private void populateEnergyConsumerSubsystemMapsLocked() {
+    private @Nullable SparseArray<EnergyConsumer> populateEnergyConsumerSubsystemMapsLocked() {
         if (mPowerStatsInternal == null) {
-            // PowerStatsInternal unavailable, don't bother populating maps.
-            mEnergyConsumerToSubsystemMap = null;
-            mSubsystemToEnergyConsumerMap = null;
-            return;
+            return null;
         }
         final EnergyConsumer[] energyConsumers = mPowerStatsInternal.getEnergyConsumerInfo();
-        if (energyConsumers == null) {
-            // EnergyConsumer data unavailable, don't bother populating maps.
-            mEnergyConsumerToSubsystemMap = null;
-            mSubsystemToEnergyConsumerMap = null;
-            return;
+        if (energyConsumers == null || energyConsumers.length == 0) {
+            return null;
         }
 
-        final int length = energyConsumers.length;
-        if (length == 0) {
-            // EnergyConsumer array empty, don't bother populating maps.
-            mEnergyConsumerToSubsystemMap = null;
-            mSubsystemToEnergyConsumerMap = null;
-            return;
-        } else {
-            mEnergyConsumerToSubsystemMap = new SparseIntArray(length);
-            mSubsystemToEnergyConsumerMap = new SparseIntArray(length);
-        }
+        // TODO: Initialize typeToIds
+        // Maps type -> {ids} (1:n map, since multiple ids might have the same type)
+        // final SparseArray<SparseIntArray> typeToIds = new SparseArray<>();
+
+        // Maps id -> EnergyConsumer (1:1 map)
+        final SparseArray<EnergyConsumer> idToConsumer = new SparseArray<>(energyConsumers.length);
 
         // Add all expected EnergyConsumers to the maps
-        for (int i = 0; i < length; i++) {
-            final EnergyConsumer consumer = energyConsumers[i];
-            switch (consumer.type) {
-                case EnergyConsumerType.DISPLAY:
-                    if (consumer.ordinal == 0) {
-                        mEnergyConsumerToSubsystemMap.put(consumer.id,
-                                MeasuredEnergyArray.SUBSYSTEM_DISPLAY);
-                        mSubsystemToEnergyConsumerMap.put(MeasuredEnergyArray.SUBSYSTEM_DISPLAY,
-                                consumer.id);
-                    } else {
-                        Slog.w(TAG, "Unexpected ordinal (" + consumer.ordinal
-                                + ") for EnergyConsumerType.DISPLAY");
-                    }
-                    break;
-                default:
-                    Slog.w(TAG, "Unexpected EnergyConsumerType (" + consumer.type + ")");
+        for (final EnergyConsumer consumer : energyConsumers) {
+            // Check for inappropriate ordinals
+            if (consumer.ordinal != 0) {
+                switch (consumer.type) {
+                    case EnergyConsumerType.OTHER:
+                    case EnergyConsumerType.CPU_CLUSTER:
+                        break;
+                    default:
+                        Slog.w(TAG, "EnergyConsumer '" + consumer.name + "' has unexpected ordinal "
+                                + consumer.ordinal + " for type " + consumer.type);
+                        continue; // Ignore this consumer
+                }
             }
-
+            idToConsumer.put(consumer.id, consumer);
+            // TODO: Also populate typeToIds map
         }
+        // TODO: Store typeToIds in mEnergyConsumerTypeToIdMap.
+        return idToConsumer;
     }
 }
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 7e65434..8b6fabd 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -967,6 +967,12 @@
         }
     }
 
+    static String broadcastDescription(BroadcastRecord r, ComponentName component) {
+        return r.intent.toString()
+                + " from " + r.callerPackage + " (pid=" + r.callingPid
+                + ", uid=" + r.callingUid + ") to " + component.flattenToShortString();
+    }
+
     final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
         BroadcastRecord r;
 
@@ -1349,14 +1355,18 @@
                         < brOptions.getMinManifestReceiverApiLevel() ||
                 info.activityInfo.applicationInfo.targetSdkVersion
                         > brOptions.getMaxManifestReceiverApiLevel())) {
+            Slog.w(TAG, "Target SDK mismatch: receiver " + info.activityInfo
+                    + " targets " + info.activityInfo.applicationInfo.targetSdkVersion
+                    + " but delivery restricted to ["
+                    + brOptions.getMinManifestReceiverApiLevel() + ", "
+                    + brOptions.getMaxManifestReceiverApiLevel()
+                    + "] broadcasting " + broadcastDescription(r, component));
             skip = true;
         }
         if (!skip && !mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
                 component.getPackageName(), info.activityInfo.applicationInfo.uid)) {
             Slog.w(TAG, "Association not allowed: broadcasting "
-                    + r.intent.toString()
-                    + " from " + r.callerPackage + " (pid=" + r.callingPid
-                    + ", uid=" + r.callingUid + ") to " + component.flattenToShortString());
+                    + broadcastDescription(r, component));
             skip = true;
         }
         if (!skip) {
@@ -1364,9 +1374,7 @@
                     r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid);
             if (skip) {
                 Slog.w(TAG, "Firewall blocked: broadcasting "
-                        + r.intent.toString()
-                        + " from " + r.callerPackage + " (pid=" + r.callingPid
-                        + ", uid=" + r.callingUid + ") to " + component.flattenToShortString());
+                        + broadcastDescription(r, component));
             }
         }
         int perm = mService.checkComponentPermission(info.activityInfo.permission,
@@ -1375,18 +1383,12 @@
         if (!skip && perm != PackageManager.PERMISSION_GRANTED) {
             if (!info.activityInfo.exported) {
                 Slog.w(TAG, "Permission Denial: broadcasting "
-                        + r.intent.toString()
-                        + " from " + r.callerPackage + " (pid=" + r.callingPid
-                        + ", uid=" + r.callingUid + ")"
-                        + " is not exported from uid " + info.activityInfo.applicationInfo.uid
-                        + " due to receiver " + component.flattenToShortString());
+                        + broadcastDescription(r, component)
+                        + " is not exported from uid " + info.activityInfo.applicationInfo.uid);
             } else {
                 Slog.w(TAG, "Permission Denial: broadcasting "
-                        + r.intent.toString()
-                        + " from " + r.callerPackage + " (pid=" + r.callingPid
-                        + ", uid=" + r.callingUid + ")"
-                        + " requires " + info.activityInfo.permission
-                        + " due to receiver " + component.flattenToShortString());
+                        + broadcastDescription(r, component)
+                        + " requires " + info.activityInfo.permission);
             }
             skip = true;
         } else if (!skip && info.activityInfo.permission != null) {
@@ -1396,13 +1398,9 @@
                     "Broadcast delivered to " + info.activityInfo.name)
                     != AppOpsManager.MODE_ALLOWED) {
                 Slog.w(TAG, "Appop Denial: broadcasting "
-                        + r.intent.toString()
-                        + " from " + r.callerPackage + " (pid="
-                        + r.callingPid + ", uid=" + r.callingUid + ")"
+                        + broadcastDescription(r, component)
                         + " requires appop " + AppOpsManager.permissionToOp(
-                                info.activityInfo.permission)
-                        + " due to registered receiver "
-                        + component.flattenToShortString());
+                                info.activityInfo.permission));
                 skip = true;
             }
         }
@@ -1520,7 +1518,7 @@
                         + info.activityInfo.packageName, e);
             }
             if (!isAvailable) {
-                if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
+                Slog.w(TAG_BROADCAST,
                         "Skipping delivery to " + info.activityInfo.packageName + " / "
                         + info.activityInfo.applicationInfo.uid
                         + " : package no longer available");
@@ -1536,6 +1534,9 @@
             if (!requestStartTargetPermissionsReviewIfNeededLocked(r,
                     info.activityInfo.packageName, UserHandle.getUserId(
                             info.activityInfo.applicationInfo.uid))) {
+                Slog.w(TAG_BROADCAST,
+                        "Skipping delivery: permission review required for "
+                                + broadcastDescription(r, component));
                 skip = true;
             }
         }
diff --git a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
index b915c0c..9e0aa32 100644
--- a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
+++ b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
@@ -17,132 +17,260 @@
 package com.android.server.am;
 
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerAttribution;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.SparseLongArray;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.power.MeasuredEnergyArray;
-import com.android.internal.power.MeasuredEnergyArray.MeasuredEnergySubsystem;
 
 import java.io.PrintWriter;
-import java.util.Arrays;
 
 /**
- * Keeps snapshots of data from previously pulled MeasuredEnergyArrays.
+ * Keeps snapshots of data from previously pulled EnergyConsumerResults.
  */
 @VisibleForTesting
 public class MeasuredEnergySnapshot {
     private static final String TAG = "MeasuredEnergySnapshot";
 
-    private static final long UNAVAILABLE = -1;
+    public static final long UNAVAILABLE = -1L;
+
+    /** Map of {@link EnergyConsumer#id} to its corresponding {@link EnergyConsumer}. */
+    private final SparseArray<EnergyConsumer> mEnergyConsumers;
+
+    /** Number of ordinals for {@link EnergyConsumerType#OTHER}. */
+    private final int mNumOtherOrdinals;
 
     /**
-     * Energy snapshots from the last time each {@link MeasuredEnergySubsystem} was updated.
+     * Energy snapshots, mapping {@link EnergyConsumer#id} to energy (UJ) from the last time
+     * each {@link EnergyConsumer} was updated.
      *
-     * Note that the snapshots for different subsystems may have been taken at different times.
+     * Note that the snapshots for different ids may have been taken at different times.
+     * Note that energies for all existing ids are stored here, including each ordinal of type
+     * {@link EnergyConsumerType#OTHER} (tracking their total energy usage).
      *
-     * A snapshot is {@link #UNAVAILABLE} if the subsystem has never been updated (ie. unsupported).
+     * If an id is not present yet, it is treated as uninitialized (energy {@link #UNAVAILABLE}).
      */
-    private final long[] mMeasuredEnergySnapshots;
+    private final SparseLongArray mMeasuredEnergySnapshots;
 
     /**
-     * Constructor that initializes to the given energyArray;
-     * all subsystems not mentioned in initialEnergyArray are set to UNAVAILABLE.
+     * Energy snapshots <b>per uid</b> from the last time each {@link EnergyConsumer} of type
+     * {@link EnergyConsumerType#OTHER} was updated.
+     * It maps each OTHER {@link EnergyConsumer#id} to a SparseLongArray, which itself maps each
+     * uid to an energy (UJ). That is,
+     * mAttributionSnapshots.get(consumerId).get(uid) = energy used by uid for this consumer.
+     *
+     * If an id is not present yet, it is treated as uninitialized (i.e. each uid is unavailable).
+     * If an id is present but a uid is not present, that uid's energy is 0.
      */
-    public MeasuredEnergySnapshot(MeasuredEnergyArray initialEnergyArray) {
-        this(MeasuredEnergyArray.NUMBER_SUBSYSTEMS, initialEnergyArray);
+    private final SparseArray<SparseLongArray> mAttributionSnapshots;
+
+    /**
+     * Constructor that initializes to the given id->EnergyConsumer map, indicating which consumers
+     * exist and what their details are.
+     */
+    MeasuredEnergySnapshot(@NonNull SparseArray<EnergyConsumer> idToConsumerMap) {
+        mEnergyConsumers = idToConsumerMap;
+        mMeasuredEnergySnapshots = new SparseLongArray(mEnergyConsumers.size());
+
+        mNumOtherOrdinals = calculateNumOtherOrdinals(idToConsumerMap);
+        mAttributionSnapshots = new SparseArray<>(mNumOtherOrdinals);
     }
 
     /**
-     * Constructor (for testing) that initializes to the given energyArray and numSubsystems;
-     * all subsystems not mentioned in initialEnergyArray are set to UNAVAILABLE.
+     * Returns the number of ordinals for {@link EnergyConsumerType#OTHER}, i.e. the number of
+     * custom energy buckets supported by the device.
      */
-    @VisibleForTesting
-    MeasuredEnergySnapshot(int numSubsystems, MeasuredEnergyArray initialEnergyArray) {
-        if (initialEnergyArray.size() > numSubsystems) {
-            throw new IllegalArgumentException("Energy array contains " + initialEnergyArray.size()
-                    + " subsystems, which exceeds the maximum allowed of " + numSubsystems);
-        }
-        mMeasuredEnergySnapshots = new long[numSubsystems];
-        Arrays.fill(mMeasuredEnergySnapshots, UNAVAILABLE);
-        fillGivenSubsystems(initialEnergyArray);
+    public int getNumOtherOrdinals() {
+        return mNumOtherOrdinals;
     }
 
-    /**
-     * For the subsystems present in energyArray, overwrites mMeasuredEnergySnapshots with their
-     * energy values from energyArray.
-     */
-    private void fillGivenSubsystems(MeasuredEnergyArray energyArray) {
-        final int size = energyArray.size();
-        for (int i = 0; i < size; i++) {
-            final int subsystem = energyArray.getSubsystem(i);
-            mMeasuredEnergySnapshots[subsystem] = energyArray.getEnergy(i);
-        }
+    /** Class for returning measured energy delta data. */
+    static class MeasuredEnergyDeltaData {
+        /** The energyUJ for {@link EnergyConsumerType#DISPLAY}. */
+        public long displayEnergyUJ = UNAVAILABLE;
+
+        /** Map of {@link EnergyConsumerType#OTHER} ordinals to their total energyUJ. */
+        public @Nullable long[] otherTotalEnergyUJ = null;
+
+        /** Map of {@link EnergyConsumerType#OTHER} ordinals to their {uid->energyUJ} maps. */
+        public @Nullable SparseLongArray[] otherUidEnergiesUJ = null;
     }
 
     /**
      * Update with the some freshly measured energies and return the difference (delta)
      * between the previously stored values and the passed-in values.
      *
-     * @param energyArray measured energy array for some (possibly not all) subsystems.
+     * @param ecrs EnergyConsumerResults for some (possibly not all) {@link EnergyConsumer}s.
+     *             Consumers that are not present are ignored (they are *not* treated as 0).
      *
-     * @return a map from the updated subsystems to their corresponding energy deltas.
-     *         Subsystems not present in energyArray will not appear.
-     *         Subsystems with no difference in energy will not appear.
-     *         Returns null, if energyArray is null.
+     * @return a MeasuredEnergyDeltaData, containing maps from the updated consumers to
+     *         their corresponding energy deltas.
+     *         Fields with no interesting data (consumers not present in ecrs or with no energy
+     *         difference) will generally be left as their default values.
+     *         otherTotalEnergyUJ and otherUidEnergiesUJ are always either both null or both of
+     *         length {@link #getNumOtherOrdinals()}.
+     *         Returns null, if ecrs is null or empty.
      */
-    public @Nullable SparseLongArray updateAndGetDelta(MeasuredEnergyArray energyArray) {
-        if (energyArray == null) {
+    public @Nullable MeasuredEnergyDeltaData updateAndGetDelta(EnergyConsumerResult[] ecrs) {
+        if (ecrs == null || ecrs.length == 0) {
             return null;
         }
-        final SparseLongArray delta = new SparseLongArray();
-        final int size = energyArray.size();
-        for (int i = 0; i < size; i++) {
-            final int updatedSubsystem = energyArray.getSubsystem(i);
-            final long newEnergyUJ = energyArray.getEnergy(i);
-            final long oldEnergyUJ = mMeasuredEnergySnapshots[updatedSubsystem];
+        final MeasuredEnergyDeltaData output = new MeasuredEnergyDeltaData();
 
-            // If this is the first valid energy, there is no delta to take.
-            if (oldEnergyUJ < 0) continue;
-            final long deltaUJ = newEnergyUJ - oldEnergyUJ;
-            if (deltaUJ == 0) continue;
-            if (deltaUJ < 0) {
-                Slog.e(TAG, "For subsystem " + updatedSubsystem + ", new energy (" + newEnergyUJ
-                        + ") is less than old energy (" + oldEnergyUJ + "). Skipping. ");
+        for (final EnergyConsumerResult ecr : ecrs) {
+            // Extract the new energy data for the current consumer.
+            final int consumerId = ecr.id;
+            final long newEnergyUJ = ecr.energyUWs;
+            final EnergyConsumerAttribution[] newAttributions = ecr.attribution;
+
+            // Look up the static information about this consumer.
+            final EnergyConsumer consumer = mEnergyConsumers.get(consumerId, null);
+            if (consumer == null) {
+                Slog.e(TAG, "updateAndGetDelta given invalid consumerId " + consumerId);
                 continue;
             }
-            delta.put(updatedSubsystem, deltaUJ);
+            final int type = consumer.type;
+            final int ordinal = consumer.ordinal;
+
+            // Look up, and update, the old energy information about this consumer.
+            final long oldEnergyUJ = mMeasuredEnergySnapshots.get(consumerId, UNAVAILABLE);
+            mMeasuredEnergySnapshots.put(consumerId, newEnergyUJ);
+            final SparseLongArray otherUidEnergies
+                    = updateAndGetDeltaForTypeOther(consumer, newAttributions);
+
+            // Everything is fully done being updated. We now calculate the delta for returning.
+
+            // NB: Since sum(attribution.energyUWs)<=energyUWs we assume that if deltaEnergy==0
+            // there's no attribution either. Technically that isn't enforced at the HAL, but we
+            // can't really trust data like that anyway.
+
+            if (oldEnergyUJ < 0) continue; // Generally happens only on initialization.
+            if (newEnergyUJ == oldEnergyUJ) continue;
+            final long deltaUJ = newEnergyUJ - oldEnergyUJ;
+            if (deltaUJ < 0) {
+                Slog.e(TAG, "EnergyConsumer " + consumer.name + ": new energy (" + newEnergyUJ
+                        + ") < old energy (" + oldEnergyUJ + "). Skipping. ");
+                continue;
+            }
+
+            switch (type) {
+                case EnergyConsumerType.DISPLAY:
+                    output.displayEnergyUJ = deltaUJ;
+                    break;
+                case EnergyConsumerType.OTHER:
+                    if (output.otherTotalEnergyUJ == null) {
+                        output.otherTotalEnergyUJ = new long[getNumOtherOrdinals()];
+                        output.otherUidEnergiesUJ = new SparseLongArray[getNumOtherOrdinals()];
+                    }
+                    output.otherTotalEnergyUJ[ordinal] = deltaUJ;
+                    output.otherUidEnergiesUJ[ordinal] = otherUidEnergies;
+                    break;
+                default:
+                    Slog.w(TAG, "Ignoring consumer " + consumer.name + " of unknown type " + type);
+
+            }
         }
-
-        fillGivenSubsystems(energyArray);
-
-        return delta;
+        return output;
     }
 
     /**
-     * Check if a subsystem's measured energy is available.
-     * @param subsystem which subsystem.
-     * @return true if subsystem is available.
+     * For a consumer of type {@link EnergyConsumerType#OTHER}, updates
+     * {@link #mAttributionSnapshots} with freshly measured energies (per uid) and returns the
+     * difference (delta) between the previously stored values and the passed-in values.
+     *
+     * @param consumerInfo a consumer of type {@link EnergyConsumerType#OTHER}.
+     * @param newAttributions Record of uids and their new energyUJ values.
+     *                        Any uid not present is treated as having energy 0.
+     *                        If null or empty, all uids are treated as having energy 0.
+     * @return A map (in the sense of {@link MeasuredEnergyDeltaData#otherUidEnergiesUJ} for this
+     *         consumer) of uid -> energyDelta, with all uids that have a non-zero energyDelta.
+     *         Returns null if no delta available to calculate.
      */
-    public boolean hasSubsystem(@MeasuredEnergySubsystem int subsystem) {
-        return mMeasuredEnergySnapshots[subsystem] != UNAVAILABLE;
+    private @Nullable SparseLongArray updateAndGetDeltaForTypeOther(
+            @NonNull EnergyConsumer consumerInfo,
+            @Nullable EnergyConsumerAttribution[] newAttributions) {
+
+        if (consumerInfo.type != EnergyConsumerType.OTHER) {
+            return null;
+        }
+        if (newAttributions == null) {
+            // Treat null as empty (i.e. all uids have 0 energy).
+            newAttributions = new EnergyConsumerAttribution[0];
+        }
+
+        // SparseLongArray mapping uid -> energyUJ (for this particular consumerId)
+        SparseLongArray uidOldEnergyMap = mAttributionSnapshots.get(consumerInfo.id, null);
+
+        // If uidOldEnergyMap wasn't present, each uid was UNAVAILABLE, so update data and return.
+        if (uidOldEnergyMap == null) {
+            uidOldEnergyMap = new SparseLongArray(newAttributions.length);
+            mAttributionSnapshots.put(consumerInfo.id, uidOldEnergyMap);
+            for (EnergyConsumerAttribution newAttribution : newAttributions) {
+                uidOldEnergyMap.put(newAttribution.uid, newAttribution.energyUWs);
+            }
+            return null;
+        }
+
+        // Map uid -> energyDelta. No initial capacity since many deltas might be 0.
+        final SparseLongArray uidEnergyDeltas = new SparseLongArray();
+
+        for (EnergyConsumerAttribution newAttribution : newAttributions) {
+            final int uid = newAttribution.uid;
+            final long newEnergyUJ = newAttribution.energyUWs;
+            // uidOldEnergyMap was present. So any particular uid that wasn't present, had 0 energy.
+            final long oldEnergyUJ = uidOldEnergyMap.get(uid, 0L);
+            uidOldEnergyMap.put(uid, newEnergyUJ);
+
+            // Everything is fully done being updated. We now calculate the delta for returning.
+            if (oldEnergyUJ < 0) continue;
+            if (newEnergyUJ == oldEnergyUJ) continue;
+            final long deltaUJ = newEnergyUJ - oldEnergyUJ;
+            if (deltaUJ < 0) {
+                Slog.e(TAG, "EnergyConsumer " + consumerInfo.name + ": new energy (" + newEnergyUJ
+                        + ") but old energy (" + oldEnergyUJ + "). Skipping. ");
+                continue;
+            }
+            uidEnergyDeltas.put(uid, deltaUJ);
+        }
+        return uidEnergyDeltas;
     }
 
     /** Dump debug data. */
     public void dump(PrintWriter pw) {
-        pw.println("Measured energy snapshot (microjoules):");
-        pw.print("   ");
-        for (int i = 0; i < MeasuredEnergyArray.NUMBER_SUBSYSTEMS; i++) {
-            final long energyUJ = mMeasuredEnergySnapshots[i];
-            if (energyUJ == UNAVAILABLE) continue;
-            pw.print(MeasuredEnergyArray.SUBSYSTEM_NAMES[i]);
-            pw.print(" : ");
-            pw.print(energyUJ);
-            if (i != MeasuredEnergyArray.NUMBER_SUBSYSTEMS - 1) {
-                pw.print(", ");
-            }
+        pw.println("Measured energy snapshot");
+        pw.println("List of EnergyConsumers:");
+        for (int i = 0; i < mEnergyConsumers.size(); i++) {
+            final int id = mEnergyConsumers.keyAt(i);
+            final EnergyConsumer consumer = mEnergyConsumers.valueAt(i);
+            pw.println(String.format("    Consumer %d is {id=%d, ordinal=%d, type=%d, name=%s}", id,
+                    consumer.id, consumer.ordinal, consumer.type, consumer.name));
         }
+        pw.println("Map of consumerIds to energy (in microjoules):");
+        for (int i = 0; i < mMeasuredEnergySnapshots.size(); i++) {
+            final int id = mMeasuredEnergySnapshots.keyAt(i);
+            final long energyUJ = mMeasuredEnergySnapshots.valueAt(i);
+            pw.println(String.format("    Consumer %d has energy %d uJ}", id, energyUJ));
+        }
+        pw.println("List of the " + mNumOtherOrdinals + " OTHER EnergyConsumers:");
+        pw.println("    " + mAttributionSnapshots);
         pw.println();
     }
+
+    /** Determines the number of ordinals for {@link EnergyConsumerType#OTHER}. */
+    private static int calculateNumOtherOrdinals(SparseArray<EnergyConsumer> idToConsumer) {
+        if (idToConsumer == null) return 0;
+        int numOtherOrdinals = 0;
+        final int size = idToConsumer.size();
+        for (int idx = 0; idx < size; idx++) {
+            final EnergyConsumer consumer = idToConsumer.valueAt(idx);
+            if (consumer.type == EnergyConsumerType.OTHER) numOtherOrdinals++;
+        }
+        return numOtherOrdinals;
+    }
 }
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 10fe1e1..c92abd4 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -3131,11 +3131,6 @@
                 return AppOpsManager.MODE_ERRORED;
             }
             final Op op = getOpLocked(ops, code, uid, true);
-            if (isOpRestrictedLocked(uid, code, packageName, bypass)) {
-                scheduleOpNotedIfNeededLocked(code, uid, packageName, flags,
-                        AppOpsManager.MODE_IGNORED);
-                return AppOpsManager.MODE_IGNORED;
-            }
             final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
             if (attributedOp.isRunning()) {
                 Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
@@ -3145,6 +3140,12 @@
 
             final int switchCode = AppOpsManager.opToSwitch(code);
             final UidState uidState = ops.uidState;
+            if (isOpRestrictedLocked(uid, code, packageName, bypass)) {
+                attributedOp.rejected(uidState.state, flags);
+                scheduleOpNotedIfNeededLocked(code, uid, packageName, flags,
+                        AppOpsManager.MODE_IGNORED);
+                return AppOpsManager.MODE_IGNORED;
+            }
             // If there is a non-default per UID policy (we set UID op mode only if
             // non-default) it takes over, otherwise use the per package policy.
             if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index baeb3fd..81e90df 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -496,6 +496,7 @@
     }
 
     void setTestHalEnabled(boolean enabled) {
+        Slog.w(mTag, "setTestHalEnabled: " + enabled);
         mTestHalEnabled = enabled;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
index 1d57073..a38da3a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
@@ -23,86 +23,85 @@
 import android.hardware.biometrics.face.SensorProps;
 import android.hardware.common.NativeHandle;
 import android.hardware.keymaster.HardwareAuthToken;
-import android.os.Binder;
-import android.os.IBinder;
+import android.util.Slog;
 
 /**
  * Test HAL that provides only no-ops.
  */
 public class TestHal extends IFace.Stub {
+    private static final String TAG = "face.aidl.TestHal";
     @Override
     public SensorProps[] getSensorProps() {
+        Slog.w(TAG, "getSensorProps");
         return new SensorProps[0];
     }
 
     @Override
     public ISession createSession(int sensorId, int userId, ISessionCallback cb) {
-        return new ISession() {
+        return new ISession.Stub() {
             @Override
             public void generateChallenge(int cookie, int timeoutSec) {
-
+                Slog.w(TAG, "generateChallenge, cookie: " + cookie);
             }
 
             @Override
             public void revokeChallenge(int cookie, long challenge) {
-
+                Slog.w(TAG, "revokeChallenge: " + challenge + ", cookie: " + cookie);
             }
 
             @Override
             public ICancellationSignal enroll(int cookie, HardwareAuthToken hat,
                     byte enrollmentType, byte[] features, NativeHandle previewSurface) {
+                Slog.w(TAG, "enroll, cookie: " + cookie);
                 return null;
             }
 
             @Override
             public ICancellationSignal authenticate(int cookie, long operationId) {
+                Slog.w(TAG, "authenticate, cookie: " + cookie);
                 return null;
             }
 
             @Override
             public ICancellationSignal detectInteraction(int cookie) {
+                Slog.w(TAG, "detectInteraction, cookie: " + cookie);
                 return null;
             }
 
             @Override
             public void enumerateEnrollments(int cookie) {
-
+                Slog.w(TAG, "enumerateEnrollments, cookie: " + cookie);
             }
 
             @Override
             public void removeEnrollments(int cookie, int[] enrollmentIds) {
-
+                Slog.w(TAG, "removeEnrollments, cookie: " + cookie);
             }
 
             @Override
             public void getFeatures(int cookie, int enrollmentId) {
-
+                Slog.w(TAG, "getFeatures, cookie: " + cookie);
             }
 
             @Override
             public void setFeature(int cookie, HardwareAuthToken hat, int enrollmentId,
                     byte feature, boolean enabled) {
-
+                Slog.w(TAG, "setFeature, cookie: " + cookie);
             }
 
             @Override
             public void getAuthenticatorId(int cookie) {
-
+                Slog.w(TAG, "getAuthenticatorId, cookie: " + cookie);
             }
 
             @Override
             public void invalidateAuthenticatorId(int cookie) {
-
+                Slog.w(TAG, "invalidateAuthenticatorId, cookie: " + cookie);
             }
 
             @Override
             public void resetLockout(int cookie, HardwareAuthToken hat) {
-
-            }
-
-            @Override
-            public IBinder asBinder() {
-                return new Binder();
+                Slog.w(TAG, "resetLockout, cookie: " + cookie);
             }
         };
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java
index bab1114d..00ca802 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java
@@ -25,10 +25,12 @@
 import android.hardware.biometrics.face.V1_1.IBiometricsFace;
 import android.os.NativeHandle;
 import android.os.RemoteException;
+import android.util.Slog;
 
 import java.util.ArrayList;
 
 public class TestHal extends IBiometricsFace.Stub {
+    private static final String TAG = "face.hidl.TestHal";
     @Nullable
     private IBiometricsFaceClientCallback mCallback;
 
@@ -47,6 +49,7 @@
 
     @Override
     public OptionalUint64 generateChallenge(int challengeTimeoutSec) {
+        Slog.w(TAG, "generateChallenge");
         final OptionalUint64 result = new OptionalUint64();
         result.status = Status.OK;
         result.value = 0;
@@ -55,6 +58,7 @@
 
     @Override
     public int enroll(ArrayList<Byte> hat, int timeoutSec, ArrayList<Integer> disabledFeatures) {
+        Slog.w(TAG, "enroll");
         return 0;
     }
 
@@ -95,16 +99,19 @@
 
     @Override
     public int enumerate() {
+        Slog.w(TAG, "enumerate");
         return 0;
     }
 
     @Override
     public int remove(int faceId) {
+        Slog.w(TAG, "remove");
         return 0;
     }
 
     @Override
     public int authenticate(long operationId) {
+        Slog.w(TAG, "authenticate");
         return 0;
     }
 
@@ -115,6 +122,7 @@
 
     @Override
     public int resetLockout(ArrayList<Byte> hat) {
+        Slog.w(TAG, "resetLockout");
         return 0;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 7e4ee9e7..73b59cf 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -476,6 +476,7 @@
     }
 
     void setTestHalEnabled(boolean enabled) {
+        Slog.w(mTag, "setTestHalEnabled, enabled");
         mTestHalEnabled = enabled;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
index a31bcdc..66b68ee 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
@@ -22,89 +22,89 @@
 import android.hardware.biometrics.fingerprint.ISessionCallback;
 import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.keymaster.HardwareAuthToken;
-import android.os.Binder;
-import android.os.IBinder;
+import android.util.Slog;
 
 /**
  * Test HAL that provides only provides no-ops.
  */
 public class TestHal extends IFingerprint.Stub {
+    private static final String TAG = "fingerprint.aidl.TestHal";
+
     @Override
     public SensorProps[] getSensorProps() {
+        Slog.w(TAG, "getSensorProps");
         return new SensorProps[0];
     }
 
     @Override
     public ISession createSession(int sensorId, int userId, ISessionCallback cb) {
-        return new ISession() {
+        return new ISession.Stub() {
             @Override
             public void generateChallenge(int cookie, int timeoutSec) {
-
+                Slog.w(TAG, "generateChallenge, cookie: " + cookie);
             }
 
             @Override
             public void revokeChallenge(int cookie, long challenge) {
-
+                Slog.w(TAG, "revokeChallenge: " + challenge + ", cookie: " + cookie);
             }
 
             @Override
             public ICancellationSignal enroll(int cookie, HardwareAuthToken hat) {
+                Slog.w(TAG, "enroll, cookie: " + cookie);
                 return null;
             }
 
             @Override
             public ICancellationSignal authenticate(int cookie, long operationId) {
+                Slog.w(TAG, "authenticate, cookie: " + cookie);
                 return null;
             }
 
             @Override
             public ICancellationSignal detectInteraction(int cookie) {
+                Slog.w(TAG, "detectInteraction, cookie: " + cookie);
                 return null;
             }
 
             @Override
             public void enumerateEnrollments(int cookie) {
-
+                Slog.w(TAG, "enumerateEnrollments, cookie: " + cookie);
             }
 
             @Override
             public void removeEnrollments(int cookie, int[] enrollmentIds) {
-
+                Slog.w(TAG, "removeEnrollments, cookie: " + cookie);
             }
 
             @Override
             public void getAuthenticatorId(int cookie) {
-
+                Slog.w(TAG, "getAuthenticatorId, cookie: " + cookie);
             }
 
             @Override
             public void invalidateAuthenticatorId(int cookie) {
-
+                Slog.w(TAG, "invalidateAuthenticatorId, cookie: " + cookie);
             }
 
             @Override
             public void resetLockout(int cookie, HardwareAuthToken hat) {
-
+                Slog.w(TAG, "resetLockout, cookie: " + cookie);
             }
 
             @Override
             public void onPointerDown(int pointerId, int x, int y, float minor, float major) {
-
+                Slog.w(TAG, "onPointerDown");
             }
 
             @Override
             public void onPointerUp(int pointerId) {
-
+                Slog.w(TAG, "onPointerUp");
             }
 
             @Override
             public void onUiReady() {
-
-            }
-
-            @Override
-            public IBinder asBinder() {
-                return new Binder();
+                Slog.w(TAG, "onUiReady");
             }
         };
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
index b7aec0e..57447f3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
@@ -21,11 +21,14 @@
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprintClientCallback;
 import android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint;
 import android.os.RemoteException;
+import android.util.Slog;
 
 /**
  * Test HAL that provides only provides no-ops.
  */
 public class TestHal extends IBiometricsFingerprint.Stub {
+    private static final String TAG = "fingerprint.hidl.TestHal";
+
     @Nullable
     private IBiometricsFingerprintClientCallback mCallback;
 
@@ -57,6 +60,7 @@
 
     @Override
     public int enroll(byte[] hat, int gid, int timeoutSec) {
+        Slog.w(TAG, "enroll");
         return 0;
     }
 
@@ -80,11 +84,13 @@
 
     @Override
     public int enumerate() {
+        Slog.w(TAG, "Enumerate");
         return 0;
     }
 
     @Override
     public int remove(int gid, int fid) {
+        Slog.w(TAG, "Remove");
         return 0;
     }
 
@@ -95,6 +101,7 @@
 
     @Override
     public int authenticate(long operationId, int gid) {
+        Slog.w(TAG, "Authenticate");
         return 0;
     }
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index fa063b2..225da7a 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -42,8 +42,8 @@
 import android.util.Slog;
 import android.util.TimeUtils;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.os.BackgroundThread;
 import com.android.server.EventLogTags;
 import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 4832e46..a62f67a 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -28,8 +28,8 @@
 import android.util.Slog;
 import android.util.Spline;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.util.Preconditions;
 import com.android.server.display.utils.Plog;
 
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
new file mode 100644
index 0000000..d4556ed
--- /dev/null
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -0,0 +1,121 @@
+/*
+ * 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.display;
+
+import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
+import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.DisplayAddress;
+
+import com.android.server.display.layout.Layout;
+
+import java.util.Arrays;
+
+/**
+ * Mapping from device states into {@link Layout}s. This allows us to map device
+ * states into specific layouts for the connected displays; particularly useful for
+ * foldable and multi-display devices where the default display and which displays are ON can
+ * change depending on the state of the device.
+ */
+class DeviceStateToLayoutMap {
+    private static final String TAG = "DeviceStateToLayoutMap";
+
+    public static final int STATE_DEFAULT = DeviceStateManager.INVALID_DEVICE_STATE;
+
+    // TODO - b/168208162 - Remove these when we check in static definitions for layouts
+    public static final int STATE_FOLDED = 100;
+    public static final int STATE_UNFOLDED = 101;
+
+    private final SparseArray<Layout> mLayoutMap = new SparseArray<>();
+
+    DeviceStateToLayoutMap(Context context) {
+        mLayoutMap.append(STATE_DEFAULT, new Layout());
+        loadFoldedDisplayConfig(context);
+    }
+
+    public void dumpLocked(IndentingPrintWriter ipw) {
+        ipw.println("DeviceStateToLayoutMap:");
+        ipw.increaseIndent();
+
+        ipw.println("Registered Layouts:");
+        for (int i = 0; i < mLayoutMap.size(); i++) {
+            ipw.println("state(" + mLayoutMap.keyAt(i) + "): " + mLayoutMap.valueAt(i));
+        }
+    }
+
+    Layout get(int state) {
+        Layout layout = mLayoutMap.get(state);
+        if (layout == null) {
+            layout = mLayoutMap.get(STATE_DEFAULT);
+        }
+        return layout;
+    }
+
+    private Layout create(int state) {
+        if (mLayoutMap.contains(state)) {
+            Slog.e(TAG, "Attempted to create a second layout for state " + state);
+            return null;
+        }
+
+        final Layout layout = new Layout();
+        mLayoutMap.append(state, layout);
+        return layout;
+    }
+
+    private void loadFoldedDisplayConfig(Context context) {
+        final String[] strDisplayIds = context.getResources().getStringArray(
+                com.android.internal.R.array.config_internalFoldedPhysicalDisplayIds);
+
+        if (strDisplayIds.length != 2 || TextUtils.isEmpty(strDisplayIds[0])
+                || TextUtils.isEmpty(strDisplayIds[1])) {
+            Slog.w(TAG, "Folded display configuration invalid: [" + Arrays.toString(strDisplayIds)
+                    + "]");
+            return;
+        }
+
+        final long[] displayIds;
+        try {
+            displayIds = new long[] {
+                Long.parseLong(strDisplayIds[0]),
+                Long.parseLong(strDisplayIds[1])
+            };
+        } catch (NumberFormatException nfe) {
+            Slog.w(TAG, "Folded display config non numerical: " + Arrays.toString(strDisplayIds));
+            return;
+        }
+
+        final int[] foldedDeviceStates = context.getResources().getIntArray(
+                com.android.internal.R.array.config_foldedDeviceStates);
+        // Only add folded states if folded state config is not empty
+        if (foldedDeviceStates.length == 0) {
+            return;
+        }
+
+        // Create the folded state layout
+        final Layout foldedLayout = create(STATE_FOLDED);
+        foldedLayout.createDisplayLocked(
+                DisplayAddress.fromPhysicalDisplayId(displayIds[0]), true /*isDefault*/);
+
+        // Create the unfolded state layout
+        final Layout unfoldedLayout = create(STATE_UNFOLDED);
+        unfoldedLayout.createDisplayLocked(
+                DisplayAddress.fromPhysicalDisplayId(displayIds[1]), true /*isDefault*/);
+    }
+}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index d687221..1b25427 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -23,8 +23,8 @@
 import android.util.Slog;
 import android.view.DisplayAddress;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.R;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.server.display.config.DisplayConfiguration;
 import com.android.server.display.config.DisplayQuirks;
 import com.android.server.display.config.HbmTiming;
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index bf16a6d..501533d 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -26,7 +26,7 @@
 import android.view.RoundedCorners;
 import android.view.Surface;
 
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
 
 import java.util.Arrays;
 import java.util.Objects;
diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
index 5c0fceb..57f4486 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceRepository.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.util.Slog;
 import android.view.Display;
+import android.view.DisplayAddress;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.display.DisplayManagerService.SyncRoot;
@@ -112,12 +113,11 @@
         }
     }
 
-    public DisplayDevice getByIdLocked(@NonNull String uniqueId) {
-        final int count = mDisplayDevices.size();
-        for (int i = 0; i < count; i++) {
-            final DisplayDevice d = mDisplayDevices.get(i);
-            if (uniqueId.equals(d.getUniqueId())) {
-                return d;
+    public DisplayDevice getByAddressLocked(@NonNull DisplayAddress address) {
+        for (int i = mDisplayDevices.size() - 1; i >= 0; i--) {
+            final DisplayDevice device = mDisplayDevices.get(i);
+            if (address.equals(device.getDisplayDeviceInfoLocked().address)) {
+                return device;
             }
         }
         return null;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 01fee56..6a22941 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -106,9 +106,9 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.AnimationThread;
@@ -1188,6 +1188,7 @@
             final LogicalDisplay display = mLogicalDisplayMapper.getLocked(device);
             final int state;
             final int displayId = display.getDisplayIdLocked();
+
             if (display.isEnabled()) {
                 state = mDisplayStates.get(displayId);
             } else {
@@ -1564,12 +1565,6 @@
         }
     }
 
-    void setFoldOverride(Boolean isFolded) {
-        synchronized (mSyncRoot) {
-            mLogicalDisplayMapper.setFoldOverrideLocked(isFolded);
-        }
-    }
-
     private void clearViewportsLocked() {
         mViewports.clear();
     }
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index aaea15a..d1d0496 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -60,8 +60,6 @@
                 return setDisplayModeDirectorLoggingEnabled(false);
             case "dwb-set-cct":
                 return setAmbientColorTemperatureOverride();
-            case "set-fold":
-                return setFoldOverride();
             default:
                 return handleDefaultCommands(cmd);
         }
@@ -92,8 +90,6 @@
         pw.println("    Disable display mode director logging.");
         pw.println("  dwb-set-cct CCT");
         pw.println("    Sets the ambient color temperature override to CCT (use -1 to disable).");
-        pw.println("  set-fold [fold|unfold|reset]");
-        pw.println("    Simulates the 'fold' state of a device. 'reset' for default behavior.");
         pw.println();
         Intent.printIntentArgsHelp(pw , "");
     }
@@ -165,26 +161,4 @@
         mService.setAmbientColorTemperatureOverride(cct);
         return 0;
     }
-
-    private int setFoldOverride() {
-        String state = getNextArg();
-        if (state == null) {
-            getErrPrintWriter().println("Error: no parameter specified for set-fold");
-            return 1;
-        }
-        final Boolean isFolded;
-        if ("fold".equals(state)) {
-            isFolded = true;
-        } else if ("unfold".equals(state)) {
-            isFolded = false;
-        } else if ("reset".equals(state)) {
-            isFolded = null;
-        } else {
-            getErrPrintWriter().println("Error: Invalid fold state request: " + state);
-            return 1;
-        }
-
-        mService.setFoldOverride(isFolded);
-        return 0;
-    }
 }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2df33652..9320f50 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -53,8 +53,8 @@
 import android.util.TimeUtils;
 import android.view.Display;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.server.LocalServices;
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 173adce..1d20d87 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -26,7 +26,7 @@
 import android.view.Choreographer;
 import android.view.Display;
 
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
 
 import java.io.PrintWriter;
 
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 73ebb2e..3b66236 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -38,7 +38,7 @@
 import android.view.RoundedCorners;
 import android.view.SurfaceControl;
 
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 80781d2..20b133c 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -32,6 +32,7 @@
 import com.android.server.wm.utils.InsetUtils;
 
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.Arrays;
 import java.util.Objects;
 
@@ -718,6 +719,7 @@
     public void dumpLocked(PrintWriter pw) {
         pw.println("mDisplayId=" + mDisplayId);
         pw.println("mLayerStack=" + mLayerStack);
+        pw.println("mIsEnabled=" + mIsEnabled);
         pw.println("mHasContent=" + mHasContent);
         pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}");
         pw.println("mRequestedColorMode=" + mRequestedColorMode);
@@ -731,4 +733,11 @@
         pw.println("mFrameRateOverrides=" + Arrays.toString(mFrameRateOverrides));
         pw.println("mPendingFrameRateOverrideUids=" + mPendingFrameRateOverrideUids);
     }
+
+    @Override
+    public String toString() {
+        StringWriter sw = new StringWriter();
+        dumpLocked(new PrintWriter(sw));
+        return sw.toString();
+    }
 }
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 16c4b26..a054db5 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -27,10 +27,10 @@
 import android.view.DisplayEventReceiver;
 import android.view.DisplayInfo;
 
+import com.android.server.display.layout.Layout;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
-import java.util.Objects;
 import java.util.function.Consumer;
 
 /**
@@ -75,40 +75,25 @@
     private final boolean mSingleDisplayDemoMode;
 
     /**
-     * Physical Display ID of the DisplayDevice to associate with the default LogicalDisplay
-     * when {@link mIsFolded} is set to {@code true}.
-     */
-    private String mDisplayIdToUseWhenFolded;
-
-    /**
-     * Physical Display ID of the DisplayDevice to associate with the default LogicalDisplay
-     * when {@link mIsFolded} is set to {@code false}.
-     */
-    private String mDisplayIdToUseWhenUnfolded;
-
-    /** Overrides the folded state of the device. For use with ADB commands. */
-    private Boolean mIsFoldedOverride;
-
-    /** Saves the last device fold state. */
-    private boolean mIsFolded;
-
-    /**
      * List of all logical displays indexed by logical display id.
      * Any modification to mLogicalDisplays must invalidate the DisplayManagerGlobal cache.
      * TODO: multi-display - Move the aforementioned comment?
      */
     private final SparseArray<LogicalDisplay> mLogicalDisplays =
             new SparseArray<LogicalDisplay>();
-    private int mNextNonDefaultDisplayId = Display.DEFAULT_DISPLAY + 1;
-    private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
 
     /** A mapping from logical display id to display group. */
     private final SparseArray<DisplayGroup> mDisplayIdToGroupMap = new SparseArray<>();
 
     private final DisplayDeviceRepository mDisplayDeviceRepo;
+    private final DeviceStateToLayoutMap mDeviceStateToLayoutMap;
     private final Listener mListener;
     private final int[] mFoldedDeviceStates;
 
+    private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+    private Layout mCurrentLayout = null;
+    private boolean mIsFolded = false;
+
     LogicalDisplayMapper(Context context, DisplayDeviceRepository repo, Listener listener) {
         mDisplayDeviceRepo = repo;
         mListener = listener;
@@ -118,7 +103,7 @@
         mFoldedDeviceStates = context.getResources().getIntArray(
                 com.android.internal.R.array.config_foldedDeviceStates);
 
-        loadFoldedDisplayConfig(context);
+        mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(context);
     }
 
     @Override
@@ -213,13 +198,12 @@
         ipw.increaseIndent();
 
         ipw.println("mSingleDisplayDemoMode=" + mSingleDisplayDemoMode);
-        ipw.println("mNextNonDefaultDisplayId=" + mNextNonDefaultDisplayId);
+
+        ipw.println("mCurrentLayout=" + mCurrentLayout);
 
         final int logicalDisplayCount = mLogicalDisplays.size();
         ipw.println();
         ipw.println("Logical Displays: size=" + logicalDisplayCount);
-
-
         for (int i = 0; i < logicalDisplayCount; i++) {
             int displayId = mLogicalDisplays.keyAt(i);
             LogicalDisplay display = mLogicalDisplays.valueAt(i);
@@ -229,6 +213,7 @@
             ipw.decreaseIndent();
             ipw.println();
         }
+        mDeviceStateToLayoutMap.dumpLocked(ipw);
     }
 
     void setDeviceStateLocked(int state) {
@@ -244,79 +229,55 @@
 
     void setDeviceFoldedLocked(boolean isFolded) {
         mIsFolded = isFolded;
-        if (mIsFoldedOverride != null) {
-            isFolded = mIsFoldedOverride.booleanValue();
+
+        // Until we have fully functioning state mapping, use hardcoded states based on isFolded
+        final int state = mIsFolded ? DeviceStateToLayoutMap.STATE_FOLDED
+                : DeviceStateToLayoutMap.STATE_UNFOLDED;
+
+        if (DEBUG) {
+            Slog.d(TAG, "New device state: " + state);
         }
 
-        if (mDisplayIdToUseWhenFolded == null || mDisplayIdToUseWhenUnfolded == null
-                || mLogicalDisplays.size() < 2) {
-            // Do nothing if this behavior is disabled or there are less than two displays.
+        final Layout layout = mDeviceStateToLayoutMap.get(state);
+        if (layout == null) {
+            return;
+        }
+        final Layout.Display displayLayout = layout.getById(Display.DEFAULT_DISPLAY);
+        if (displayLayout == null) {
+            return;
+        }
+        final DisplayDevice newDefaultDevice =
+                mDisplayDeviceRepo.getByAddressLocked(displayLayout.getAddress());
+        if (newDefaultDevice == null) {
             return;
         }
 
-        final DisplayDevice deviceFolded =
-                mDisplayDeviceRepo.getByIdLocked(mDisplayIdToUseWhenFolded);
-        final DisplayDevice deviceUnfolded =
-                mDisplayDeviceRepo.getByIdLocked(mDisplayIdToUseWhenUnfolded);
-        if (deviceFolded == null || deviceUnfolded == null) {
-            // If the expected devices for folding functionality are not present, return early.
-            return;
-        }
-
-        // Find the associated LogicalDisplays for the configured "folding" DeviceDisplays.
-        final LogicalDisplay displayFolded = getLocked(deviceFolded);
-        final LogicalDisplay displayUnfolded = getLocked(deviceUnfolded);
-        if (displayFolded == null || displayUnfolded == null) {
-            // If the expected displays are not present, return early.
-            return;
-        }
-
-        // Find out which display is currently default and which is disabled.
         final LogicalDisplay defaultDisplay = mLogicalDisplays.get(Display.DEFAULT_DISPLAY);
-        final LogicalDisplay disabledDisplay;
-        if (defaultDisplay == displayFolded) {
-            disabledDisplay = displayUnfolded;
-        } else if (defaultDisplay == displayUnfolded) {
-            disabledDisplay = displayFolded;
-        } else {
-            // If neither folded or unfolded displays are currently set to the default display, we
-            // are in an unknown state and it's best to log the error and bail.
-            Slog.e(TAG, "Unexpected: when attempting to swap displays, neither of the two"
-                    + " configured displays were set up as the default display. Default: "
-                    + defaultDisplay.getDisplayInfoLocked() + ",  ConfiguredDisplays: [ folded="
-                    + displayFolded.getDisplayInfoLocked() + ", unfolded="
-                    + displayUnfolded.getDisplayInfoLocked() + " ]");
+        mCurrentLayout = layout;
+
+        // If we're already set up accurately, return early
+        if (defaultDisplay.getPrimaryDisplayDeviceLocked() == newDefaultDevice) {
             return;
         }
 
-        if (isFolded == (defaultDisplay == displayFolded)) {
-            // Nothing to do, already in the right state.
+        // We need to swap the default display's display-device with the one that is supposed
+        // to be the default in the new layout.
+        final LogicalDisplay displayToSwap = getLocked(newDefaultDevice);
+        if (displayToSwap == null) {
+            Slog.w(TAG, "Canceling display swap - unexpected empty second display for: "
+                    + newDefaultDevice);
             return;
         }
-
-        // Everything was checked and we need to swap, lets swap.
-        displayFolded.swapDisplaysLocked(displayUnfolded);
+        defaultDisplay.swapDisplaysLocked(displayToSwap);
 
         // We ensure that the non-default Display is always forced to be off. This was likely
         // already done in a previous iteration, but we do it with each swap in case something in
         // the underlying LogicalDisplays changed: like LogicalDisplay recreation, for example.
         defaultDisplay.setEnabled(true);
-        disabledDisplay.setEnabled(false);
+        displayToSwap.setEnabled(false);
 
         // Update the world
         updateLogicalDisplaysLocked();
-
-        if (DEBUG) {
-            Slog.d(TAG, "Folded displays: isFolded: " + isFolded + ", defaultDisplay? "
-                    + defaultDisplay.getDisplayInfoLocked());
-        }
-    }
-
-    void setFoldOverrideLocked(Boolean isFolded) {
-        if (!Objects.equals(isFolded, mIsFoldedOverride)) {
-            mIsFoldedOverride = isFolded;
-            setDeviceFoldedLocked(mIsFolded);
-        }
     }
 
     private void handleDisplayDeviceAddedLocked(DisplayDevice device) {
@@ -333,7 +294,7 @@
             return;
         }
 
-        final int displayId = assignDisplayIdLocked(isDefault);
+        final int displayId = Layout.assignDisplayIdLocked(isDefault);
         final int layerStack = assignLayerStackLocked(displayId);
 
         final DisplayGroup displayGroup;
@@ -356,8 +317,15 @@
             return;
         }
 
-        mLogicalDisplays.put(displayId, display);
+        // For foldable devices, we start the internal non-default displays as disabled.
+        // TODO - b/168208162 - this will be removed when we recalculate the layout with each
+        // display-device addition.
+        if (mFoldedDeviceStates.length > 0 && deviceInfo.type == Display.TYPE_INTERNAL
+                && !isDefault) {
+            display.setEnabled(false);
+        }
 
+        mLogicalDisplays.put(displayId, display);
         displayGroup.addDisplayLocked(display);
         mDisplayIdToGroupMap.append(displayId, displayGroup);
 
@@ -375,6 +343,10 @@
             mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
                     LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED);
         }
+
+        if (DEBUG) {
+            Slog.d(TAG, "New Display added: " + display);
+        }
     }
 
     /**
@@ -466,10 +438,6 @@
         }
     }
 
-    private int assignDisplayIdLocked(boolean isDefault) {
-        return isDefault ? Display.DEFAULT_DISPLAY : mNextNonDefaultDisplayId++;
-    }
-
     private int assignDisplayGroupIdLocked(boolean isDefault) {
         return isDefault ? Display.DEFAULT_DISPLAY_GROUP : mNextNonDefaultGroupId++;
     }
@@ -480,21 +448,6 @@
         return displayId;
     }
 
-    private void loadFoldedDisplayConfig(Context context) {
-        final String[] displayIds = context.getResources().getStringArray(
-                com.android.internal.R.array.config_internalFoldedPhysicalDisplayIds);
-
-        if (displayIds.length != 2 || TextUtils.isEmpty(displayIds[0])
-                || TextUtils.isEmpty(displayIds[1])) {
-            Slog.w(TAG, "Folded display configuration invalid: [" + Arrays.toString(displayIds)
-                    + "]");
-            return;
-        }
-
-        mDisplayIdToUseWhenFolded = displayIds[0];
-        mDisplayIdToUseWhenUnfolded = displayIds[1];
-    }
-
     public interface Listener {
         void onLogicalDisplayEventLocked(LogicalDisplay display, int event);
         void onDisplayGroupEventLocked(int groupId, int event);
diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java
index 7916d81..26004a8 100644
--- a/services/core/java/com/android/server/display/RampAnimator.java
+++ b/services/core/java/com/android/server/display/RampAnimator.java
@@ -20,7 +20,7 @@
 import android.util.FloatProperty;
 import android.view.Choreographer;
 
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
 
 /**
  * A custom animator that progressively updates a property value at
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
new file mode 100644
index 0000000..18f39e6
--- /dev/null
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -0,0 +1,154 @@
+/*
+ * 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.display.layout;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+import android.view.DisplayAddress;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Holds a collection of {@link Display}s. A single instance of this class describes
+ * how to organize one or more DisplayDevices into LogicalDisplays for a particular device
+ * state. For example, there may be one instance of this class to describe display layout when
+ * a foldable device is folded, and a second instance for when the device is unfolded.
+ */
+public class Layout {
+    private static final String TAG = "Layout";
+    private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1;
+
+    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();
+    }
+
+    /**
+     * Creates a simple 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.
+     * @return The new layout.
+     */
+    public Display createDisplayLocked(
+            @NonNull DisplayAddress address, boolean isDefault) {
+        if (contains(address)) {
+            Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
+            return null;
+        }
+
+        // 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);
+            isDefault = false;
+        }
+
+        // Assign a logical display ID and create the new display.
+        // Note that the logical display ID is saved into the layout, so when switching between
+        // different layouts, a logical display can be destroyed and later recreated with the
+        // same logical display ID.
+        final int logicalDisplayId = assignDisplayIdLocked(isDefault);
+        final Display layout = new Display(address, logicalDisplayId);
+
+        mDisplays.add(layout);
+        return layout;
+    }
+
+    /**
+     * @param address The address to check.
+     *
+     * @return True if the specified address is used in this layout.
+     */
+    public boolean contains(@NonNull DisplayAddress address) {
+        final int size = mDisplays.size();
+        for (int i = 0; i < size; i++) {
+            if (address.equals(mDisplays.get(i).getAddress())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @param id The display ID to check.
+     *
+     * @return The display corresponding to the specified display ID.
+     */
+    public Display getById(int id) {
+        for (int i = 0; i < mDisplays.size(); i++) {
+            Display layout = mDisplays.get(i);
+            if (id == layout.getLogicalDisplayId()) {
+                return layout;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @param index The index of the display to return.
+     *
+     * @return the display at the specified index.
+     */
+    public Display getAt(int index) {
+        return mDisplays.get(index);
+    }
+
+    /**
+     * @return The number of displays defined for this layout.
+     */
+    public int size() {
+        return mDisplays.size();
+    }
+
+    /**
+     * Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s.
+     */
+    public static class Display {
+        private final DisplayAddress mAddress;
+        private final int mLogicalDisplayId;
+
+        Display(@NonNull DisplayAddress address, int logicalDisplayId) {
+            mAddress = address;
+            mLogicalDisplayId = logicalDisplayId;
+        }
+
+        @Override
+        public String toString() {
+            return "{addr: " + mAddress + ", dispId: " + mLogicalDisplayId + "}";
+        }
+
+        public DisplayAddress getAddress() {
+            return mAddress;
+        }
+
+        public int getLogicalDisplayId() {
+            return mLogicalDisplayId;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
index 909fcda..66fc0d9 100644
--- a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
+++ b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
@@ -17,6 +17,7 @@
 
 import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_UNKNOWN;
 
+import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
 import android.util.SparseIntArray;
@@ -108,7 +109,10 @@
     private void resetPowerStatus(List<HdmiDeviceInfo> deviceInfos) {
         mPowerStatus.clear();
         for (HdmiDeviceInfo info : deviceInfos) {
-            mPowerStatus.append(info.getLogicalAddress(), info.getDevicePowerStatus());
+            if (localDevice().mService.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0
+                    || info.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+                mPowerStatus.append(info.getLogicalAddress(), info.getDevicePowerStatus());
+            }
         }
     }
 
@@ -117,19 +121,22 @@
                 localDevice().mService.getHdmiCecNetwork().getDeviceInfoList(false);
         resetPowerStatus(deviceInfos);
         for (HdmiDeviceInfo info : deviceInfos) {
-            final int logicalAddress = info.getLogicalAddress();
-            sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(),
-                    logicalAddress),
-                    new SendMessageCallback() {
-                        @Override
-                        public void onSendCompleted(int error) {
-                            // If fails to send <Give Device Power Status>,
-                            // update power status into UNKNOWN.
-                            if (error != SendMessageResult.SUCCESS) {
-                               updatePowerStatus(logicalAddress, POWER_STATUS_UNKNOWN, true);
+            if (localDevice().mService.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0
+                    || info.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+                final int logicalAddress = info.getLogicalAddress();
+                sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(),
+                        logicalAddress),
+                        new SendMessageCallback() {
+                            @Override
+                            public void onSendCompleted(int error) {
+                                // If fails to send <Give Device Power Status>,
+                                // update power status into UNKNOWN.
+                                if (error != SendMessageResult.SUCCESS) {
+                                    updatePowerStatus(logicalAddress, POWER_STATUS_UNKNOWN, true);
+                                }
                             }
-                        }
-                    });
+                        });
+            }
         }
 
         mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 1a4c8b7..00ae30e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -5246,12 +5246,7 @@
                     imeTracing.startTrace(null);
                 });
             }
-        }
-        doDump(fd, pw, args, asProto);
-    }
 
-    private void doDump(FileDescriptor fd, PrintWriter pw, String[] args, boolean useProto) {
-        if (useProto) {
             final ProtoOutputStream proto = new ProtoOutputStream(fd);
             dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
             proto.flush();
diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java
index 43c965d..42b0add 100644
--- a/services/core/java/com/android/server/lights/LightsService.java
+++ b/services/core/java/com/android/server/lights/LightsService.java
@@ -36,9 +36,9 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.SystemService;
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 28dc516..47cb43e 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -1253,7 +1253,8 @@
         ArrayMap<String, ArrayMap<String, LocationEventLog.AggregateStats>> aggregateStats =
                 mEventLog.copyAggregateStats();
         for (int i = 0; i < aggregateStats.size(); i++) {
-            ipw.println(aggregateStats.keyAt(i));
+            ipw.print(aggregateStats.keyAt(i));
+            ipw.println(":");
             ipw.increaseIndent();
             ArrayMap<String, LocationEventLog.AggregateStats> providerStats =
                     aggregateStats.valueAt(i);
diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
index 67060fc..865d41f 100644
--- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
@@ -164,6 +164,7 @@
         if (Build.IS_DEBUGGABLE || D) {
             addLogEvent(EVENT_PROVIDER_DELIVER_LOCATION, provider, numLocations, identity);
         }
+        getAggregateStats(provider, identity.getPackageName()).markLocationDelivered();
     }
 
     /** Logs that the location power save mode has changed. */
@@ -397,6 +398,8 @@
         private int mActiveRequestCount;
         @GuardedBy("this")
         private int mForegroundRequestCount;
+        @GuardedBy("this")
+        private int mDeliveredLocationCount;
 
         @GuardedBy("this")
         private long mFastestIntervalMs = Long.MAX_VALUE;
@@ -464,6 +467,10 @@
             Preconditions.checkState(mForegroundRequestCount >= 0);
         }
 
+        synchronized void markLocationDelivered() {
+            mDeliveredLocationCount++;
+        }
+
         public synchronized void updateTotals() {
             if (mAddedRequestCount > 0) {
                 long realtimeMs = SystemClock.elapsedRealtime();
@@ -488,7 +495,8 @@
                     + intervalToString(mSlowestIntervalMs)
                     + ", total/active/foreground duration = " + formatDuration(mAddedTimeTotalMs)
                     + "/" + formatDuration(mActiveTimeTotalMs) + "/"
-                    + formatDuration(mForegroundTimeTotalMs);
+                    + formatDuration(mForegroundTimeTotalMs) + ", locations = "
+                    + mDeliveredLocationCount;
         }
 
         private static String intervalToString(long intervalMs) {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 28c90e9..cb9793f 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -2372,10 +2372,17 @@
     public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
             String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
         enforceShell();
+        final int origPid = Binder.getCallingPid();
+        final int origUid = Binder.getCallingUid();
+
+        // The original identity is an opaque integer.
         final long origId = Binder.clearCallingIdentity();
+        Slog.e(TAG, "Caller pid " + origPid + " Caller uid " + origUid);
         try {
-            (new LockSettingsShellCommand(new LockPatternUtils(mContext))).exec(
-                    this, in, out, err, args, callback, resultReceiver);
+            final LockSettingsShellCommand command =
+                    new LockSettingsShellCommand(new LockPatternUtils(mContext), mContext, origPid,
+                            origUid);
+            command.exec(this, in, out, err, args, callback, resultReceiver);
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index 834cf05..67fae05 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -21,8 +21,11 @@
 
 import android.app.ActivityManager;
 import android.app.admin.PasswordMetrics;
+import android.content.Context;
 import android.os.ShellCommand;
+import android.os.SystemProperties;
 import android.text.TextUtils;
+import android.util.Slog;
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
@@ -43,15 +46,25 @@
     private static final String COMMAND_VERIFY = "verify";
     private static final String COMMAND_GET_DISABLED = "get-disabled";
     private static final String COMMAND_REMOVE_CACHE = "remove-cache";
+    private static final String COMMAND_SET_ROR_PROVIDER_PACKAGE =
+            "set-resume-on-reboot-provider-package";
     private static final String COMMAND_HELP = "help";
 
     private int mCurrentUserId;
     private final LockPatternUtils mLockPatternUtils;
+    private final Context mContext;
+    private final int mCallingPid;
+    private final int mCallingUid;
+
     private String mOld = "";
     private String mNew = "";
 
-    LockSettingsShellCommand(LockPatternUtils lockPatternUtils) {
+    LockSettingsShellCommand(LockPatternUtils lockPatternUtils, Context context, int callingPid,
+            int callingUid) {
         mLockPatternUtils = lockPatternUtils;
+        mCallingPid = callingPid;
+        mCallingUid = callingUid;
+        mContext = context;
     }
 
     @Override
@@ -68,6 +81,7 @@
                     case COMMAND_HELP:
                     case COMMAND_GET_DISABLED:
                     case COMMAND_SET_DISABLED:
+                    case COMMAND_SET_ROR_PROVIDER_PACKAGE:
                         break;
                     default:
                         getErrPrintWriter().println(
@@ -80,6 +94,9 @@
                 case COMMAND_REMOVE_CACHE:
                     runRemoveCache();
                     return 0;
+                case COMMAND_SET_ROR_PROVIDER_PACKAGE:
+                    runSetResumeOnRebootProviderPackage();
+                    return 0;
                 case COMMAND_HELP:
                     onHelp();
                     return 0;
@@ -170,6 +187,9 @@
             pw.println("");
             pw.println("  remove-cache [--user USER_ID]");
             pw.println("    Removes cached unified challenge for the managed profile.");
+            pw.println("  set-resume-on-reboot-provider-package <package_name>");
+            pw.println("    Sets the package name for server based resume on reboot service "
+                    + "provider.");
             pw.println("");
         }
     }
@@ -256,6 +276,17 @@
         return true;
     }
 
+    private boolean runSetResumeOnRebootProviderPackage() {
+        final String packageName = mNew;
+        String name = ResumeOnRebootServiceProvider.PROP_ROR_PROVIDER_PACKAGE;
+        Slog.i(TAG, "Setting " +  name + " to " + packageName);
+
+        mContext.enforcePermission(android.Manifest.permission.BIND_RESUME_ON_REBOOT_SERVICE,
+                mCallingPid, mCallingUid, TAG);
+        SystemProperties.set(name, packageName);
+        return true;
+    }
+
     private boolean runClear() {
         LockscreenCredential none = LockscreenCredential.createNone();
         if (!isNewCredentialSufficient(none)) {
diff --git a/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java
index a1e18bd..9c471b8 100644
--- a/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java
+++ b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java
@@ -31,6 +31,7 @@
 import android.os.ParcelableException;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.service.resumeonreboot.IResumeOnRebootService;
@@ -55,6 +56,10 @@
             Manifest.permission.BIND_RESUME_ON_REBOOT_SERVICE;
     private static final String TAG = "ResumeOnRebootServiceProvider";
 
+    // The system property name that overrides the default service provider package name.
+    static final String PROP_ROR_PROVIDER_PACKAGE =
+            "persist.sys.resume_on_reboot_provider_package";
+
     private final Context mContext;
     private final PackageManager mPackageManager;
 
@@ -72,12 +77,19 @@
     private ServiceInfo resolveService() {
         Intent intent = new Intent();
         intent.setAction(ResumeOnRebootService.SERVICE_INTERFACE);
-        if (PROVIDER_PACKAGE != null && !PROVIDER_PACKAGE.equals("")) {
-            intent.setPackage(PROVIDER_PACKAGE);
+        int queryFlag = PackageManager.GET_SERVICES;
+        String testAppName = SystemProperties.get(PROP_ROR_PROVIDER_PACKAGE, "");
+        if (!testAppName.isEmpty()) {
+            Slog.i(TAG, "Using test app: " + testAppName);
+            intent.setPackage(testAppName);
+        } else {
+            queryFlag |= PackageManager.MATCH_SYSTEM_ONLY;
+            if (PROVIDER_PACKAGE != null && !PROVIDER_PACKAGE.equals("")) {
+                intent.setPackage(PROVIDER_PACKAGE);
+            }
         }
 
-        List<ResolveInfo> resolvedIntents =
-                mPackageManager.queryIntentServices(intent, PackageManager.MATCH_SYSTEM_ONLY);
+        List<ResolveInfo> resolvedIntents = mPackageManager.queryIntentServices(intent, queryFlag);
         for (ResolveInfo resolvedInfo : resolvedIntents) {
             if (resolvedInfo.serviceInfo != null
                     && PROVIDER_REQUIRED_PERMISSION.equals(resolvedInfo.serviceInfo.permission)) {
@@ -120,6 +132,7 @@
             if (mServiceConnection != null) {
                 mContext.unbindService(mServiceConnection);
             }
+            mBinder = null;
         }
 
         /** Bind to the service */
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4c3dfbf..5703ffe 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -110,6 +110,8 @@
 import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
 import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
 import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
+import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_ANIM_BUFFER;
+import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
 import static com.android.server.utils.PriorityDump.PRIORITY_ARG;
 import static com.android.server.utils.PriorityDump.PRIORITY_ARG_CRITICAL;
 import static com.android.server.utils.PriorityDump.PRIORITY_ARG_NORMAL;
@@ -284,7 +286,6 @@
 import com.android.server.notification.toast.TextToastRecord;
 import com.android.server.notification.toast.ToastRecord;
 import com.android.server.pm.PackageManagerService;
-import com.android.server.policy.PhoneWindowManager;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.uri.UriGrantsManagerInternal;
 import com.android.server.utils.quota.MultiRateLimiter;
@@ -358,7 +359,7 @@
     private static final int MESSAGE_RECONSIDER_RANKING = 1000;
     private static final int MESSAGE_RANKING_SORT = 1001;
 
-    static final int LONG_DELAY = PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
+    static final int LONG_DELAY = TOAST_WINDOW_TIMEOUT - TOAST_WINDOW_ANIM_BUFFER; // 3.5 seconds
     static final int SHORT_DELAY = 2000; // 2 seconds
 
     // 1 second past the ANR timeout.
@@ -3056,7 +3057,7 @@
                     // If the callback fails, this will remove it from the list, so don't
                     // assume that it's valid after this.
                     if (index == 0) {
-                        showNextToastLocked();
+                        showNextToastLocked(false);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(callingId);
@@ -7392,7 +7393,7 @@
     }
 
     @GuardedBy("mToastQueue")
-    void showNextToastLocked() {
+    void showNextToastLocked(boolean lastToastWasTextRecord) {
         if (mIsCurrentToastShown) {
             return; // Don't show the same toast twice.
         }
@@ -7406,7 +7407,7 @@
                     mToastRateLimiter.isWithinQuota(userId, record.pkg, TOAST_QUOTA_TAG);
 
             if (tryShowToast(record, rateLimitingEnabled, isWithinQuota)) {
-                scheduleDurationReachedLocked(record);
+                scheduleDurationReachedLocked(record, lastToastWasTextRecord);
                 mIsCurrentToastShown = true;
                 if (rateLimitingEnabled) {
                     mToastRateLimiter.noteEvent(userId, record.pkg, TOAST_QUOTA_TAG);
@@ -7477,7 +7478,7 @@
             // Show the next one. If the callback fails, this will remove
             // it from the list, so don't assume that the list hasn't changed
             // after this point.
-            showNextToastLocked();
+            showNextToastLocked(lastToast instanceof TextToastRecord);
         }
     }
 
@@ -7491,7 +7492,7 @@
     }
 
     @GuardedBy("mToastQueue")
-    private void scheduleDurationReachedLocked(ToastRecord r)
+    private void scheduleDurationReachedLocked(ToastRecord r, boolean lastToastWasTextRecord)
     {
         mHandler.removeCallbacksAndMessages(r);
         Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r);
@@ -7501,6 +7502,14 @@
         // preference.
         delay = mAccessibilityManager.getRecommendedTimeoutMillis(delay,
                 AccessibilityManager.FLAG_CONTENT_TEXT);
+
+        if (lastToastWasTextRecord) {
+            delay += 250; // delay to account for previous toast's "out" animation
+        }
+        if (r instanceof TextToastRecord) {
+            delay += 333; // delay to account for this toast's "in" animation
+        }
+
         mHandler.sendMessageDelayed(m, delay);
     }
 
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 402f646..af0aa76 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -217,7 +217,7 @@
             // trade-off worth doing to save boot time work.
             int result = pm.performDexOptWithStatus(new DexoptOptions(
                     pkg,
-                    PackageManagerService.REASON_BOOT,
+                    PackageManagerService.REASON_POST_BOOT,
                     DexoptOptions.DEXOPT_BOOT_COMPLETE));
             if (result == PackageDexOptimizer.DEX_OPT_PERFORMED)  {
                 updatedPackages.add(pkg);
diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
index 52fdc79..308e815 100644
--- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java
+++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
@@ -27,6 +27,8 @@
 import android.content.pm.IDataLoaderStatusListener;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -45,12 +47,20 @@
 public class DataLoaderManagerService extends SystemService {
     private static final String TAG = "DataLoaderManager";
     private final Context mContext;
+    private final HandlerThread mThread;
+    private final Handler mHandler;
     private final DataLoaderManagerBinderService mBinderService;
     private SparseArray<DataLoaderServiceConnection> mServiceConnections = new SparseArray<>();
 
     public DataLoaderManagerService(Context context) {
         super(context);
         mContext = context;
+
+        mThread = new HandlerThread(TAG);
+        mThread.start();
+
+        mHandler = new Handler(mThread.getLooper());
+
         mBinderService = new DataLoaderManagerBinderService();
     }
 
@@ -62,7 +72,7 @@
     final class DataLoaderManagerBinderService extends IDataLoaderManager.Stub {
         @Override
         public boolean bindToDataLoader(int dataLoaderId, DataLoaderParamsParcel params,
-                IDataLoaderStatusListener listener) {
+                long bindDelayMs, IDataLoaderStatusListener listener) {
             synchronized (mServiceConnections) {
                 if (mServiceConnections.get(dataLoaderId) != null) {
                     return true;
@@ -76,19 +86,21 @@
             }
 
             // Binds to the specific data loader service.
-            DataLoaderServiceConnection connection = new DataLoaderServiceConnection(dataLoaderId,
-                    listener);
+            final DataLoaderServiceConnection connection = new DataLoaderServiceConnection(
+                    dataLoaderId, listener);
 
-            Intent intent = new Intent();
+            final Intent intent = new Intent();
             intent.setComponent(dataLoaderComponent);
-            if (!mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE,
-                    UserHandle.of(UserHandle.getCallingUserId()))) {
-                Slog.e(TAG,
-                        "Failed to bind to: " + dataLoaderComponent + " for ID=" + dataLoaderId);
-                mContext.unbindService(connection);
-                return false;
-            }
-            return true;
+
+            return mHandler.postDelayed(() -> {
+                if (!mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE,
+                        mHandler, UserHandle.of(UserHandle.getCallingUserId()))) {
+                    Slog.e(TAG,
+                            "Failed to bind to: " + dataLoaderComponent + " for ID="
+                                    + dataLoaderId);
+                    mContext.unbindService(connection);
+                }
+            }, bindDelayMs);
         }
 
         /**
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index f9ae9b8..e91bb46 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -929,6 +929,7 @@
                 // Flag for bubble to make behaviour match documentLaunchMode=always.
                 intents[0].addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
                 intents[0].addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                intents[0].putExtra(Intent.EXTRA_IS_BUBBLED, true);
             }
 
             intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index fc02b34..b9e3e0f 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -687,7 +687,8 @@
         boolean generateCompactDex = true;
         switch (compilationReason) {
             case PackageManagerService.REASON_FIRST_BOOT:
-            case PackageManagerService.REASON_BOOT:
+            case PackageManagerService.REASON_BOOT_AFTER_OTA:
+            case PackageManagerService.REASON_POST_BOOT:
             case PackageManagerService.REASON_INSTALL:
                  generateCompactDex = false;
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 7c42569..0ce2673 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -3778,7 +3778,9 @@
             }
         }
 
-        if (!dataLoaderManager.bindToDataLoader(sessionId, params.getData(), statusListener)) {
+        final long bindDelayMs = 0;
+        if (!dataLoaderManager.bindToDataLoader(sessionId, params.getData(), bindDelayMs,
+                statusListener)) {
             throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
                     "Failed to initialize data loader");
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 4db6550..7b9cf73 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -780,17 +780,18 @@
     // Compilation reasons.
     public static final int REASON_UNKNOWN = -1;
     public static final int REASON_FIRST_BOOT = 0;
-    public static final int REASON_BOOT = 1;
-    public static final int REASON_INSTALL = 2;
-    public static final int REASON_INSTALL_FAST = 3;
-    public static final int REASON_INSTALL_BULK = 4;
-    public static final int REASON_INSTALL_BULK_SECONDARY = 5;
-    public static final int REASON_INSTALL_BULK_DOWNGRADED = 6;
-    public static final int REASON_INSTALL_BULK_SECONDARY_DOWNGRADED = 7;
-    public static final int REASON_BACKGROUND_DEXOPT = 8;
-    public static final int REASON_AB_OTA = 9;
-    public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 10;
-    public static final int REASON_SHARED = 11;
+    public static final int REASON_BOOT_AFTER_OTA = 1;
+    public static final int REASON_POST_BOOT = 2;
+    public static final int REASON_INSTALL = 3;
+    public static final int REASON_INSTALL_FAST = 4;
+    public static final int REASON_INSTALL_BULK = 5;
+    public static final int REASON_INSTALL_BULK_SECONDARY = 6;
+    public static final int REASON_INSTALL_BULK_DOWNGRADED = 7;
+    public static final int REASON_INSTALL_BULK_SECONDARY_DOWNGRADED = 8;
+    public static final int REASON_BACKGROUND_DEXOPT = 9;
+    public static final int REASON_AB_OTA = 10;
+    public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 11;
+    public static final int REASON_SHARED = 12;
 
     public static final int REASON_LAST = REASON_SHARED;
 
@@ -11637,10 +11638,7 @@
         //       first boot, as they do not have profile data.
         boolean causeFirstBoot = isFirstBoot() || mIsPreNUpgrade;
 
-        // We need to re-extract after a pruned cache, as AoT-ed files will be out of date.
-        boolean causePrunedCache = VMRuntime.didPruneDalvikCache();
-
-        if (!causeUpgrade && !causeFirstBoot && !causePrunedCache) {
+        if (!causeUpgrade && !causeFirstBoot) {
             return;
         }
 
@@ -11657,7 +11655,7 @@
 
         final long startTime = System.nanoTime();
         final int[] stats = performDexOptUpgrade(pkgs, mIsPreNUpgrade /* showDialog */,
-                    causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT,
+                    causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT_AFTER_OTA,
                     false /* bootComplete */);
 
         final int elapsedTimeSeconds =
@@ -26213,30 +26211,7 @@
 
         @Override
         public void setKeepUninstalledPackages(final List<String> packageList) {
-            Preconditions.checkNotNull(packageList);
-            List<String> removedFromList = null;
-            synchronized (mLock) {
-                if (mKeepUninstalledPackages != null) {
-                    final int packagesCount = mKeepUninstalledPackages.size();
-                    for (int i = 0; i < packagesCount; i++) {
-                        String oldPackage = mKeepUninstalledPackages.get(i);
-                        if (packageList != null && packageList.contains(oldPackage)) {
-                            continue;
-                        }
-                        if (removedFromList == null) {
-                            removedFromList = new ArrayList<>();
-                        }
-                        removedFromList.add(oldPackage);
-                    }
-                }
-                mKeepUninstalledPackages = new ArrayList<>(packageList);
-                if (removedFromList != null) {
-                    final int removedCount = removedFromList.size();
-                    for (int i = 0; i < removedCount; i++) {
-                        deletePackageIfUnusedLPr(removedFromList.get(i));
-                    }
-                }
-            }
+            PackageManagerService.this.setKeepUninstalledPackagesInternal(packageList);
         }
 
         @Override
@@ -27734,6 +27709,43 @@
     public DomainVerificationService.Connection getDomainVerificationConnection() {
         return mDomainVerificationConnection;
     }
+
+    @Override
+    public void setKeepUninstalledPackages(List<String> packageList) {
+        mContext.enforceCallingPermission(
+                Manifest.permission.KEEP_UNINSTALLED_PACKAGES,
+                "setKeepUninstalledPackages requires KEEP_UNINSTALLED_PACKAGES permission");
+        Objects.requireNonNull(packageList);
+
+        setKeepUninstalledPackagesInternal(packageList);
+    }
+
+    private void setKeepUninstalledPackagesInternal(List<String> packageList) {
+        Preconditions.checkNotNull(packageList);
+        List<String> removedFromList = null;
+        synchronized (mLock) {
+            if (mKeepUninstalledPackages != null) {
+                final int packagesCount = mKeepUninstalledPackages.size();
+                for (int i = 0; i < packagesCount; i++) {
+                    String oldPackage = mKeepUninstalledPackages.get(i);
+                    if (packageList != null && packageList.contains(oldPackage)) {
+                        continue;
+                    }
+                    if (removedFromList == null) {
+                        removedFromList = new ArrayList<>();
+                    }
+                    removedFromList.add(oldPackage);
+                }
+            }
+            mKeepUninstalledPackages = new ArrayList<>(packageList);
+            if (removedFromList != null) {
+                final int removedCount = removedFromList.size();
+                for (int i = 0; i < removedCount; i++) {
+                    deletePackageIfUnusedLPr(removedFromList.get(i));
+                }
+            }
+        }
+    }
 }
 
 interface PackageSender {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index 9cd55a6..636db11 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -29,7 +29,8 @@
     // Names for compilation reasons.
     public static final String REASON_STRINGS[] = {
         "first-boot",
-        "boot",
+        "boot-after-ota",
+        "post-boot",
         "install",
         "install-fast",
         "install-bulk",
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 139654e..3576950 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -587,7 +587,7 @@
     private static final int TRON_COMPILATION_REASON_ERROR = 0;
     private static final int TRON_COMPILATION_REASON_UNKNOWN = 1;
     private static final int TRON_COMPILATION_REASON_FIRST_BOOT = 2;
-    private static final int TRON_COMPILATION_REASON_BOOT = 3;
+    private static final int TRON_COMPILATION_REASON_BOOT_DEPRECATED_SINCE_S = 3;
     private static final int TRON_COMPILATION_REASON_INSTALL = 4;
     private static final int TRON_COMPILATION_REASON_BG_DEXOPT = 5;
     private static final int TRON_COMPILATION_REASON_AB_OTA = 6;
@@ -605,6 +605,8 @@
     private static final int TRON_COMPILATION_REASON_INSTALL_BULK_DOWNGRADED_WITH_DM = 18;
     private static final int
             TRON_COMPILATION_REASON_INSTALL_BULK_SECONDARY_DOWNGRADED_WITH_DM = 19;
+    private static final int TRON_COMPILATION_REASON_BOOT_AFTER_OTA = 20;
+    private static final int TRON_COMPILATION_REASON_POST_BOOT = 21;
 
     // The annotation to add as a suffix to the compilation reason when dexopt was
     // performed with dex metadata.
@@ -618,7 +620,8 @@
             case "unknown" : return TRON_COMPILATION_REASON_UNKNOWN;
             case "error" : return TRON_COMPILATION_REASON_ERROR;
             case "first-boot" : return TRON_COMPILATION_REASON_FIRST_BOOT;
-            case "boot" : return TRON_COMPILATION_REASON_BOOT;
+            case "boot-after-ota": return TRON_COMPILATION_REASON_BOOT_AFTER_OTA;
+            case "post-boot" : return TRON_COMPILATION_REASON_POST_BOOT;
             case "install" : return TRON_COMPILATION_REASON_INSTALL;
             case "bg-dexopt" : return TRON_COMPILATION_REASON_BG_DEXOPT;
             case "ab-ota" : return TRON_COMPILATION_REASON_AB_OTA;
diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java
index 30c334d..32bee58 100644
--- a/services/core/java/com/android/server/pm/permission/Permission.java
+++ b/services/core/java/com/android/server/pm/permission/Permission.java
@@ -38,6 +38,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Collection;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Permission definition.
@@ -345,6 +346,14 @@
         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_ROLE) != 0;
     }
 
+    public boolean isKnownSigner() {
+        return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER) != 0;
+    }
+
+    public Set<String> getKnownCerts() {
+        return mPermissionInfo.knownCerts;
+    }
+
     public void transfer(@NonNull String oldPackageName, @NonNull String newPackageName) {
         if (!oldPackageName.equals(mPermissionInfo.packageName)) {
             return;
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index aff87111..0669581 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -3438,6 +3438,11 @@
             // Any pre-installed system app is allowed to get this permission.
             allowed = true;
         }
+        if (!allowed && bp.isKnownSigner()) {
+            // If the permission is to be granted to a known signer then check if any of this
+            // app's signing certificates are in the trusted certificate digest Set.
+            allowed = pkg.getSigningDetails().hasAncestorOrSelfWithDigest(bp.getKnownCerts());
+        }
         // Deferred to be checked under permission data lock inside restorePermissionState().
         //if (!allowed && bp.isDevelopment()) {
         //    // For development permissions, a development permission
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index bc81961..6a441f1 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -346,8 +346,21 @@
     /** Amount of time (in milliseconds) to wait for windows drawn before powering on. */
     static final int WAITING_FOR_DRAWN_TIMEOUT = 1000;
 
-    /** Amount of time (in milliseconds) a toast window can be shown. */
-    public static final int TOAST_WINDOW_TIMEOUT = 3500; // 3.5 seconds
+    /**
+      * Extra time for additional SystemUI animations.
+      * <p>Since legacy apps can add Toast windows directly instead of using Toast APIs,
+      * {@link DisplayPolicy} ensures that the window manager removes toast windows after
+      * TOAST_WINDOW_TIMEOUT. We increase this timeout by TOAST_WINDOW_ANIM_BUFFER to account for
+      * SystemUI's in/out toast animations, so that the toast text is still shown for a minimum
+      * of 3.5 seconds and the animations are finished before window manager removes the window.
+      */
+    public static final int TOAST_WINDOW_ANIM_BUFFER = 600;
+
+    /**
+      * Amount of time (in milliseconds) a toast window can be shown before it's automatically
+      * removed by window manager.
+      */
+    public static final int TOAST_WINDOW_TIMEOUT = 3500 + TOAST_WINDOW_ANIM_BUFFER;
 
     /**
      * Lock protecting internal state.  Must not call out into window
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 8e0d632..88fdc4a 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -90,11 +90,11 @@
 import android.view.Display;
 import android.view.KeyEvent;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index ea41980..b7285d5 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -73,6 +73,8 @@
     private TimerTrigger mTimerTrigger;
     @Nullable
     private StatsPullAtomCallbackImpl mPullAtomCallback;
+    @Nullable
+    private PowerStatsInternal mPowerStatsInternal;
 
     @VisibleForTesting
     static class Injector {
@@ -125,8 +127,8 @@
         }
 
         StatsPullAtomCallbackImpl createStatsPullerImpl(Context context,
-                IPowerStatsHALWrapper powerStatsHALWrapper) {
-            return new StatsPullAtomCallbackImpl(context, powerStatsHALWrapper);
+                PowerStatsInternal powerStatsInternal) {
+            return new StatsPullAtomCallbackImpl(context, powerStatsInternal);
         }
     }
 
@@ -175,21 +177,14 @@
     @Override
     public void onStart() {
         if (getPowerStatsHal().isInitialized()) {
-            // Only create internal service if PowerStatsHal is available.
-            publishLocalService(PowerStatsInternal.class, new LocalService());
+            mPowerStatsInternal = new LocalService();
+            publishLocalService(PowerStatsInternal.class, mPowerStatsInternal);
         }
         publishBinderService(Context.POWER_STATS_SERVICE, new BinderService());
     }
 
     private void onSystemServicesReady() {
-        if (getPowerStatsHal().isInitialized()) {
-            if (DEBUG) Slog.d(TAG, "Starting PowerStatsService statsd pullers");
-
-            // Only start statsd pullers if initialization is successful.
-            mPullAtomCallback = mInjector.createStatsPullerImpl(mContext, getPowerStatsHal());
-        } else {
-            Slog.e(TAG, "Failed to start PowerStatsService statsd pullers");
-        }
+        mPullAtomCallback = mInjector.createStatsPullerImpl(mContext, mPowerStatsInternal);
     }
 
     private void onBootCompleted() {
diff --git a/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java b/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java
index 7c6999a..bdabefb 100644
--- a/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java
+++ b/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java
@@ -24,26 +24,31 @@
 import android.hardware.power.stats.State;
 import android.hardware.power.stats.StateResidency;
 import android.hardware.power.stats.StateResidencyResult;
+import android.power.PowerStatsInternal;
+import android.util.Slog;
 import android.util.StatsEvent;
 
 import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
 
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 
 /**
  * StatsPullAtomCallbackImpl is responsible implementing the stats pullers for
  * SUBSYSTEM_SLEEP_STATE and ON_DEVICE_POWER_MEASUREMENT statsd atoms.
  */
 public class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback {
+    private static final String TAG = StatsPullAtomCallbackImpl.class.getSimpleName();
     private Context mContext;
-    private IPowerStatsHALWrapper mPowerStatsHALWrapper;
+    private PowerStatsInternal mPowerStatsInternal;
     private Map<Integer, Channel> mChannels = new HashMap();
     private Map<Integer, String> mEntityNames = new HashMap();
-    private Map<Integer, Map<Integer, String>> mStateNames = new HashMap();;
+    private Map<Integer, Map<Integer, String>> mStateNames = new HashMap();
+    private static final int STATS_PULL_TIMEOUT_MILLIS = 2000;
+    private static final boolean DEBUG = false;
 
     @Override
     public int onPullAtom(int atomTag, List<StatsEvent> data) {
@@ -57,21 +62,28 @@
         }
     }
 
-    private void initPullOnDevicePowerMeasurement() {
-        Channel[] channels = mPowerStatsHALWrapper.getEnergyMeterInfo();
-        if (channels == null) {
-            return;
+    private boolean initPullOnDevicePowerMeasurement() {
+        Channel[] channels = mPowerStatsInternal.getEnergyMeterInfo();
+        if (channels == null || channels.length == 0) {
+            Slog.e(TAG, "Failed to init OnDevicePowerMeasurement puller");
+            return false;
         }
 
         for (int i = 0; i < channels.length; i++) {
             final Channel channel = channels[i];
             mChannels.put(channel.id, channel);
         }
+
+        return true;
     }
 
     private int pullOnDevicePowerMeasurement(int atomTag, List<StatsEvent> events) {
-        EnergyMeasurement[] energyMeasurements = mPowerStatsHALWrapper.readEnergyMeter(new int[0]);
-        if (energyMeasurements == null) {
+        final EnergyMeasurement[] energyMeasurements;
+        try {
+            energyMeasurements = mPowerStatsInternal.readEnergyMeterAsync(new int[0])
+                    .get(STATS_PULL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        } catch (Exception e) {
+            Slog.e(TAG, "Failed to readEnergyMeterAsync", e);
             return StatsManager.PULL_SKIP;
         }
 
@@ -91,10 +103,11 @@
         return StatsManager.PULL_SUCCESS;
     }
 
-    private void initSubsystemSleepState() {
-        PowerEntity[] entities = mPowerStatsHALWrapper.getPowerEntityInfo();
-        if (entities == null) {
-            return;
+    private boolean initSubsystemSleepState() {
+        PowerEntity[] entities = mPowerStatsInternal.getPowerEntityInfo();
+        if (entities == null || entities.length == 0) {
+            Slog.e(TAG, "Failed to init SubsystemSleepState puller");
+            return false;
         }
 
         for (int i = 0; i < entities.length; i++) {
@@ -108,13 +121,20 @@
             mEntityNames.put(entity.id, entity.name);
             mStateNames.put(entity.id, states);
         }
+
+        return true;
     }
 
     private int pullSubsystemSleepState(int atomTag, List<StatsEvent> events) {
-        StateResidencyResult[] results =  mPowerStatsHALWrapper.getStateResidency(new int[0]);
-        if (results == null) {
+        final StateResidencyResult[] results;
+        try {
+            results = mPowerStatsInternal.getStateResidencyAsync(new int[0])
+                    .get(STATS_PULL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        } catch (Exception e) {
+            Slog.e(TAG, "Failed to getStateResidencyAsync", e);
             return StatsManager.PULL_SKIP;
         }
+
         for (int i = 0; i < results.length; i++) {
             final StateResidencyResult result = results[i];
             for (int j = 0; j < result.stateResidencyData.length; j++) {
@@ -131,22 +151,33 @@
         return StatsManager.PULL_SUCCESS;
     }
 
-    public StatsPullAtomCallbackImpl(Context context, IPowerStatsHALWrapper powerStatsHALWrapper) {
+    public StatsPullAtomCallbackImpl(Context context, PowerStatsInternal powerStatsInternal) {
+        if (DEBUG) Slog.d(TAG, "Starting PowerStatsService statsd pullers");
+
         mContext = context;
-        mPowerStatsHALWrapper = powerStatsHALWrapper;
-        initPullOnDevicePowerMeasurement();
-        initSubsystemSleepState();
+        mPowerStatsInternal = powerStatsInternal;
+
+        if (powerStatsInternal == null) {
+            Slog.e(TAG, "Failed to start PowerStatsService statsd pullers");
+            return;
+        }
 
         StatsManager manager = mContext.getSystemService(StatsManager.class);
-        manager.setPullAtomCallback(
-                FrameworkStatsLog.SUBSYSTEM_SLEEP_STATE,
-                null, // use default PullAtomMetadata values
-                ConcurrentUtils.DIRECT_EXECUTOR,
-                this);
-        manager.setPullAtomCallback(
-                FrameworkStatsLog.ON_DEVICE_POWER_MEASUREMENT,
-                null, // use default PullAtomMetadata values
-                ConcurrentUtils.DIRECT_EXECUTOR,
-                this);
+
+        if (initPullOnDevicePowerMeasurement()) {
+            manager.setPullAtomCallback(
+                    FrameworkStatsLog.ON_DEVICE_POWER_MEASUREMENT,
+                    null, // use default PullAtomMetadata values
+                    ConcurrentUtils.DIRECT_EXECUTOR,
+                    this);
+        }
+
+        if (initSubsystemSleepState()) {
+            manager.setPullAtomCallback(
+                    FrameworkStatsLog.SUBSYSTEM_SLEEP_STATE,
+                    null, // use default PullAtomMetadata values
+                    ConcurrentUtils.DIRECT_EXECUTOR,
+                    this);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 539b413..0d43600 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -1612,9 +1612,6 @@
         // Aggregate times for the same uids.
         SparseArray<long[]> aggregated = new SparseArray<>();
         mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> {
-            // For uids known to be aggregated from many entries allow mutating in place to avoid
-            // many copies. Otherwise, copy before aggregating.
-            boolean mutateInPlace = false;
             if (UserHandle.isIsolated(uid)) {
                 // Skip individual isolated uids because they are recycled and quickly removed from
                 // the underlying data source.
@@ -1622,26 +1619,18 @@
             } else if (UserHandle.isSharedAppGid(uid)) {
                 // All shared app gids are accounted together.
                 uid = LAST_SHARED_APPLICATION_GID;
-                mutateInPlace = true;
             } else {
                 // Everything else is accounted under their base uid.
                 uid = UserHandle.getAppId(uid);
             }
 
             long[] aggCpuFreqTimeMs = aggregated.get(uid);
-            if (aggCpuFreqTimeMs != null) {
-                if (!mutateInPlace) {
-                    aggCpuFreqTimeMs = Arrays.copyOf(aggCpuFreqTimeMs, cpuFreqTimeMs.length);
-                    aggregated.put(uid, aggCpuFreqTimeMs);
-                }
-                for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
-                    aggCpuFreqTimeMs[freqIndex] += cpuFreqTimeMs[freqIndex];
-                }
-            } else {
-                if (mutateInPlace) {
-                    cpuFreqTimeMs = Arrays.copyOf(cpuFreqTimeMs, cpuFreqTimeMs.length);
-                }
-                aggregated.put(uid, cpuFreqTimeMs);
+            if (aggCpuFreqTimeMs == null) {
+                aggCpuFreqTimeMs = new long[cpuFreqTimeMs.length];
+                aggregated.put(uid, aggCpuFreqTimeMs);
+            }
+            for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
+                aggCpuFreqTimeMs[freqIndex] += cpuFreqTimeMs[freqIndex];
             }
         });
 
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index bbbd19f..b210339 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
 import android.app.timedetector.GnssTimeSuggestion;
 import android.app.timedetector.ITimeDetectorService;
 import android.app.timedetector.ManualTimeSuggestion;
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index 6a4c276..792f372 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -18,7 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
 import android.app.timedetector.GnssTimeSuggestion;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index 7cd4184..c4c620c 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -23,7 +23,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AlarmManager;
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
 import android.app.timedetector.GnssTimeSuggestion;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index bee66637..04dac7c 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -92,13 +92,6 @@
     @GuardedBy("mLock")
     private boolean mForceStop;
 
-    // TODO(b/159207608): Remove this constructor once VibratorService is removed
-    public VibrationThread(Vibration vib, VibratorController vibrator,
-            PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService,
-            VibrationCallbacks callbacks) {
-        this(vib, toSparseArray(vibrator), wakeLock, batteryStatsService, callbacks);
-    }
-
     public VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators,
             PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService,
             VibrationCallbacks callbacks) {
@@ -286,12 +279,6 @@
         return filteredEffects;
     }
 
-    private static SparseArray<VibratorController> toSparseArray(VibratorController controller) {
-        SparseArray<VibratorController> array = new SparseArray<>(1);
-        array.put(controller.getVibratorInfo().getId(), controller);
-        return array;
-    }
-
     /**
      * Get the duration the vibrator will be on for given {@code waveform}, starting at {@code
      * startIndex} until the next time it's vibrating amplitude is zero.
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 3456e51..79f8229 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -772,11 +772,6 @@
             newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT, true);
         }
         newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT, new IntentSender(target));
-        ActivityOptions options = mRequest.activityOptions.getOptions(mRequest.intent,
-                mRequest.activityInfo,
-                mService.getProcessController(mRequest.caller),
-                mSupervisor);
-        newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_ACTIVITY_OPTIONS, options.toBundle());
         heavy.updateIntentForHeavyWeightActivity(newIntent);
         newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP,
                 mRequest.activityInfo.packageName);
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index 4e47984..a6029cd 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -73,10 +73,6 @@
               static_cast<uint8_t>(aidl::Effect::TEXTURE_TICK));
 
 static std::shared_ptr<vibrator::HalController> findVibrator(int32_t vibratorId) {
-    // TODO(b/167946816): remove this once VibratorService is removed.
-    if (vibratorId < 0) {
-        return std::move(std::make_unique<vibrator::HalController>());
-    }
     vibrator::ManagerHalController* manager = android_server_VibratorManagerService_getManager();
     if (manager == nullptr) {
         return nullptr;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index bd10992..07eb7bf 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -16328,8 +16328,10 @@
             setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
             setLocale(provisioningParams.getLocale());
 
+            final int deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
+                    ? UserHandle.USER_SYSTEM : caller.getUserId();
             if (!removeNonRequiredAppsForManagedDevice(
-                    caller.getUserId(),
+                    deviceOwnerUserId,
                     provisioningParams.isLeaveAllSystemAppsEnabled(),
                     deviceAdmin)) {
                 throw new ServiceSpecificException(
@@ -16337,15 +16339,16 @@
                         "PackageManager failed to remove non required apps.");
             }
 
+
             if (!setActiveAdminAndDeviceOwner(
-                    caller.getUserId(), deviceAdmin, provisioningParams.getOwnerName())) {
+                    deviceOwnerUserId, deviceAdmin, provisioningParams.getOwnerName())) {
                 throw new ServiceSpecificException(
                         PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED,
                         "Failed to set device owner.");
             }
 
             disallowAddUser();
-            setAdminCanGrantSensorsPermissionForUserUnchecked(caller.getUserId(),
+            setAdminCanGrantSensorsPermissionForUserUnchecked(deviceOwnerUserId,
                     provisioningParams.canDeviceOwnerGrantSensorsPermissions());
         } catch (Exception e) {
             DevicePolicyEventLogger
@@ -16388,30 +16391,42 @@
     }
 
     private boolean removeNonRequiredAppsForManagedDevice(
-            int userId, boolean leaveAllSystemAppsEnabled, ComponentName admin) {
+            @UserIdInt int userId, boolean leaveAllSystemAppsEnabled, ComponentName admin) {
         Set<String> packagesToDelete = leaveAllSystemAppsEnabled
                 ? Collections.emptySet()
                 : mOverlayPackagesProvider.getNonRequiredApps(
                         admin, userId, ACTION_PROVISION_MANAGED_DEVICE);
+
+        removeNonInstalledPackages(packagesToDelete, userId);
         if (packagesToDelete.isEmpty()) {
+            Slog.i(LOG_TAG, "No packages to delete on user " + userId);
             return true;
         }
+
         NonRequiredPackageDeleteObserver packageDeleteObserver =
                 new NonRequiredPackageDeleteObserver(packagesToDelete.size());
         for (String packageName : packagesToDelete) {
-            if (isPackageInstalledForUser(packageName, userId)) {
-                Slog.i(LOG_TAG, "Deleting package [" + packageName + "] as user " + userId);
-                mContext.getPackageManager().deletePackageAsUser(
-                        packageName,
-                        packageDeleteObserver,
-                        PackageManager.DELETE_SYSTEM_APP,
-                        userId);
-            }
+            Slog.i(LOG_TAG, "Deleting package [" + packageName + "] as user " + userId);
+            mContext.getPackageManager().deletePackageAsUser(
+                    packageName,
+                    packageDeleteObserver,
+                    PackageManager.DELETE_SYSTEM_APP,
+                    userId);
         }
         Slog.i(LOG_TAG, "Waiting for non required apps to be deleted");
         return packageDeleteObserver.awaitPackagesDeletion();
     }
 
+    private void removeNonInstalledPackages(Set<String> packages, @UserIdInt int userId) {
+        final Set<String> toBeRemoved = new HashSet<>();
+        for (String packageName : packages) {
+            if (!isPackageInstalledForUser(packageName, userId)) {
+                toBeRemoved.add(packageName);
+            }
+        }
+        packages.removeAll(toBeRemoved);
+    }
+
     private void disallowAddUser() {
         if (mInjector.userManagerIsHeadlessSystemUserMode()) {
             Slog.i(LOG_TAG, "Not setting DISALLOW_ADD_USER on headless system user mode.");
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 56cb3d1..886c1e5 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -69,6 +69,14 @@
     static constexpr auto progressUpdateInterval = 1000ms;
     static constexpr auto perUidTimeoutOffset = progressUpdateInterval * 2;
     static constexpr auto minPerUidTimeout = progressUpdateInterval * 3;
+
+    // If DL was up and not crashing for 10mins, we consider it healthy and reset all delays.
+    static constexpr auto healthyDataLoaderUptime = 10min;
+    // 10s, 100s (~2min), 1000s (~15min), 10000s (~3hrs)
+    static constexpr auto minBindDelay = 10s;
+    static constexpr auto maxBindDelay = 10000s;
+    static constexpr auto bindDelayMultiplier = 10;
+    static constexpr auto bindDelayJitterDivider = 10;
 };
 
 static const Constants& constants() {
@@ -386,6 +394,28 @@
     dprintf(fd, "}\n");
 }
 
+bool IncrementalService::needStartDataLoaderLocked(IncFsMount& ifs) {
+    if (ifs.dataLoaderStub->params().packageName == Constants::systemPackage) {
+        return true;
+    }
+
+    // Check all permanent binds.
+    for (auto&& [_, bindPoint] : ifs.bindPoints) {
+        if (bindPoint.kind != BindKind::Permanent) {
+            continue;
+        }
+        const auto progress = getLoadingProgressFromPath(ifs, bindPoint.sourceDir,
+                                                         /*stopOnFirstIncomplete=*/true);
+        if (!progress.isError() && !progress.fullyLoaded()) {
+            LOG(INFO) << "Non system mount: [" << bindPoint.sourceDir
+                      << "], partial progress: " << progress.getProgress() * 100 << "%";
+            return true;
+        }
+    }
+
+    return false;
+}
+
 void IncrementalService::onSystemReady() {
     if (mSystemReady.exchange(true)) {
         return;
@@ -396,8 +426,11 @@
         std::lock_guard l(mLock);
         mounts.reserve(mMounts.size());
         for (auto&& [id, ifs] : mMounts) {
-            if (ifs->mountId == id &&
-                ifs->dataLoaderStub->params().packageName == Constants::systemPackage) {
+            if (ifs->mountId != id) {
+                continue;
+            }
+
+            if (needStartDataLoaderLocked(*ifs)) {
                 mounts.push_back(ifs);
             }
         }
@@ -1539,6 +1572,11 @@
     return std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
 }
 
+template <class Duration>
+static constexpr auto castToMs(Duration d) {
+    return std::chrono::duration_cast<std::chrono::milliseconds>(d);
+}
+
 // Extract lib files from zip, create new files in incfs and write data to them
 // Lib files should be placed next to the APK file in the following matter:
 // Example:
@@ -2134,9 +2172,43 @@
                << status << " (current " << mCurrentStatus << ")";
 }
 
+Milliseconds IncrementalService::DataLoaderStub::updateBindDelay() {
+    std::unique_lock lock(mMutex);
+    const auto previousBindTs = mPreviousBindTs;
+    const auto now = Clock::now();
+    mPreviousBindTs = now;
+
+    const auto nonCrashingInterval = std::max(castToMs(now - previousBindTs), 100ms);
+    if (previousBindTs.time_since_epoch() == Clock::duration::zero() ||
+        nonCrashingInterval > Constants::healthyDataLoaderUptime) {
+        mPreviousBindDelay = 0ms;
+        return mPreviousBindDelay;
+    }
+
+    constexpr auto minBindDelayMs = castToMs(Constants::minBindDelay);
+    constexpr auto maxBindDelayMs = castToMs(Constants::maxBindDelay);
+
+    const auto bindDelayMs =
+            std::min(std::max(mPreviousBindDelay * Constants::bindDelayMultiplier, minBindDelayMs),
+                     maxBindDelayMs)
+                    .count();
+    const auto bindDelayJitterRangeMs = bindDelayMs / Constants::bindDelayJitterDivider;
+    const auto bindDelayJitterMs = rand() % (bindDelayJitterRangeMs * 2) - bindDelayJitterRangeMs;
+    mPreviousBindDelay = std::chrono::milliseconds(bindDelayMs + bindDelayJitterMs);
+
+    return mPreviousBindDelay;
+}
+
 bool IncrementalService::DataLoaderStub::bind() {
+    const auto bindDelay = updateBindDelay();
+    if (bindDelay > 1s) {
+        LOG(INFO) << "Delaying bind to " << mParams.packageName << " by "
+                  << bindDelay.count() / 1000 << "s";
+    }
+
     bool result = false;
-    auto status = mService.mDataLoaderManager->bindToDataLoader(id(), mParams, this, &result);
+    auto status = mService.mDataLoaderManager->bindToDataLoader(id(), mParams, bindDelay.count(),
+                                                                this, &result);
     if (!status.isOk() || !result) {
         LOG(ERROR) << "Failed to bind a data loader for mount " << id();
         return false;
@@ -2249,7 +2321,8 @@
 
         listener = mStatusListener;
 
-        if (mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE) {
+        if (mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE ||
+            mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE) {
             // For unavailable, unbind from DataLoader to ensure proper re-commit.
             setTargetStatusLocked(IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
         }
@@ -2544,6 +2617,9 @@
         dprintf(fd, "          blockIndex: %d\n", pendingRead.block);
         dprintf(fd, "          bootClockTsUs: %lld\n", (long long)pendingRead.bootClockTsUs);
     }
+    dprintf(fd, "        bind: %llds ago (delay: %llds)\n",
+            (long long)(elapsedMcs(mPreviousBindTs, Clock::now()) / 1000000),
+            (long long)(mPreviousBindDelay.count() / 1000));
     dprintf(fd, "      }\n");
     const auto& params = mParams;
     dprintf(fd, "      dataLoaderParams: {\n");
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index 5d53bac..459ed32 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -245,7 +245,6 @@
         void setTargetStatusLocked(int status);
 
         bool fsmStep();
-        bool fsmStep(int currentStatus, int targetStatus);
 
         void onHealthStatus(StorageHealthListener healthListener, int healthStatus);
         void updateHealthStatus(bool baseline = false);
@@ -259,6 +258,8 @@
 
         BootClockTsUs getOldestPendingReadTs();
 
+        Milliseconds updateBindDelay();
+
         void registerForPendingReads();
         void unregisterFromPendingReads();
 
@@ -276,6 +277,9 @@
         int mTargetStatus = content::pm::IDataLoaderStatusListener::DATA_LOADER_DESTROYED;
         TimePoint mTargetStatusTs = {};
 
+        TimePoint mPreviousBindTs = {};
+        Milliseconds mPreviousBindDelay = {};
+
         std::string mHealthPath;
         incfs::UniqueControl mHealthControl;
         struct {
@@ -370,6 +374,8 @@
     void addBindMountRecordLocked(IncFsMount& ifs, StorageId storage, std::string&& metadataName,
                                   std::string&& source, std::string&& target, BindKind kind);
 
+    bool needStartDataLoaderLocked(IncFsMount& ifs);
+
     DataLoaderStubPtr prepareDataLoader(IncFsMount& ifs,
                                         content::pm::DataLoaderParamsParcel&& params,
                                         const DataLoaderStatusListener* statusListener = nullptr,
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index 25d3f77..659d650 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -70,9 +70,10 @@
     ~RealDataLoaderManager() = default;
     binder::Status bindToDataLoader(MountId mountId,
                                     const content::pm::DataLoaderParamsParcel& params,
+                                    int bindDelayMs,
                                     const sp<content::pm::IDataLoaderStatusListener>& listener,
                                     bool* _aidl_return) const final {
-        return mInterface->bindToDataLoader(mountId, params, listener, _aidl_return);
+        return mInterface->bindToDataLoader(mountId, params, bindDelayMs, listener, _aidl_return);
     }
     binder::Status getDataLoader(MountId mountId,
                                  sp<content::pm::IDataLoader>* _aidl_return) const final {
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index 71fd3ac..d60035a 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -63,7 +63,7 @@
 public:
     virtual ~DataLoaderManagerWrapper() = default;
     virtual binder::Status bindToDataLoader(
-            MountId mountId, const content::pm::DataLoaderParamsParcel& params,
+            MountId mountId, const content::pm::DataLoaderParamsParcel& params, int bindDelayMs,
             const sp<content::pm::IDataLoaderStatusListener>& listener, bool* result) const = 0;
     virtual binder::Status getDataLoader(MountId mountId,
                                          sp<content::pm::IDataLoader>* result) const = 0;
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 8713f9d..ab491ef 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -208,8 +208,9 @@
         EXPECT_TRUE(mDataLoaderHolder != nullptr);
     }
 
-    MOCK_CONST_METHOD4(bindToDataLoader,
+    MOCK_CONST_METHOD5(bindToDataLoader,
                        binder::Status(int32_t mountId, const DataLoaderParamsParcel& params,
+                                      int bindDelayMs,
                                       const sp<IDataLoaderStatusListener>& listener,
                                       bool* _aidl_return));
     MOCK_CONST_METHOD2(getDataLoader,
@@ -217,11 +218,11 @@
     MOCK_CONST_METHOD1(unbindFromDataLoader, binder::Status(int32_t mountId));
 
     void bindToDataLoaderSuccess() {
-        ON_CALL(*this, bindToDataLoader(_, _, _, _))
+        ON_CALL(*this, bindToDataLoader(_, _, _, _, _))
                 .WillByDefault(Invoke(this, &MockDataLoaderManager::bindToDataLoaderOk));
     }
     void bindToDataLoaderFails() {
-        ON_CALL(*this, bindToDataLoader(_, _, _, _))
+        ON_CALL(*this, bindToDataLoader(_, _, _, _, _))
                 .WillByDefault(Return(
                         (binder::Status::fromExceptionCode(1, String8("failed to prepare")))));
     }
@@ -234,6 +235,7 @@
                 .WillByDefault(Invoke(this, &MockDataLoaderManager::unbindFromDataLoaderOk));
     }
     binder::Status bindToDataLoaderOk(int32_t mountId, const DataLoaderParamsParcel& params,
+                                      int bindDelayMs,
                                       const sp<IDataLoaderStatusListener>& listener,
                                       bool* _aidl_return) {
         mId = mountId;
@@ -245,6 +247,40 @@
         }
         return binder::Status::ok();
     }
+    binder::Status bindToDataLoaderOkWith10sDelay(int32_t mountId,
+                                                  const DataLoaderParamsParcel& params,
+                                                  int bindDelayMs,
+                                                  const sp<IDataLoaderStatusListener>& listener,
+                                                  bool* _aidl_return) {
+        CHECK(1000 * 9 <= bindDelayMs && bindDelayMs <= 1000 * 11) << bindDelayMs;
+        return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
+    }
+    binder::Status bindToDataLoaderOkWith100sDelay(int32_t mountId,
+                                                   const DataLoaderParamsParcel& params,
+                                                   int bindDelayMs,
+                                                   const sp<IDataLoaderStatusListener>& listener,
+                                                   bool* _aidl_return) {
+        CHECK(1000 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11) << bindDelayMs;
+        return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
+    }
+    binder::Status bindToDataLoaderOkWith1000sDelay(int32_t mountId,
+                                                    const DataLoaderParamsParcel& params,
+                                                    int bindDelayMs,
+                                                    const sp<IDataLoaderStatusListener>& listener,
+                                                    bool* _aidl_return) {
+        CHECK(1000 * 9 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11 * 11) << bindDelayMs;
+        return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
+    }
+    binder::Status bindToDataLoaderOkWith10000sDelay(int32_t mountId,
+                                                     const DataLoaderParamsParcel& params,
+                                                     int bindDelayMs,
+                                                     const sp<IDataLoaderStatusListener>& listener,
+                                                     bool* _aidl_return) {
+        CHECK(1000 * 9 * 9 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11 * 11 * 11)
+                << bindDelayMs;
+        return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
+    }
+
     binder::Status getDataLoaderOk(int32_t mountId, sp<IDataLoader>* _aidl_return) {
         *_aidl_return = mDataLoader;
         return binder::Status::ok();
@@ -261,6 +297,9 @@
     void setDataLoaderStatusUnavailable() {
         mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE);
     }
+    void setDataLoaderStatusUnrecoverable() {
+        mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE);
+    }
     binder::Status unbindFromDataLoaderOk(int32_t id) {
         if (mDataLoader) {
             if (auto status = mDataLoader->destroy(id); !status.isOk()) {
@@ -676,7 +715,7 @@
 
 TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsFails) {
     mVold->mountIncFsFails();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0);
     TemporaryDir tempDir;
     int storageId =
             mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
@@ -686,7 +725,7 @@
 
 TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsInvalidControlParcel) {
     mVold->mountIncFsInvalidControlParcel();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
     TemporaryDir tempDir;
     int storageId =
@@ -698,7 +737,7 @@
 TEST_F(IncrementalServiceTest, testCreateStorageMakeFileFails) {
     mVold->mountIncFsSuccess();
     mIncFs->makeFileFails();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
     EXPECT_CALL(*mVold, unmountIncFs(_));
     TemporaryDir tempDir;
@@ -712,7 +751,7 @@
     mVold->mountIncFsSuccess();
     mIncFs->makeFileSuccess();
     mVold->bindMountFails();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
     EXPECT_CALL(*mVold, unmountIncFs(_));
     TemporaryDir tempDir;
@@ -727,7 +766,7 @@
     mIncFs->makeFileSuccess();
     mVold->bindMountSuccess();
     mDataLoaderManager->bindToDataLoaderFails();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(0);
     EXPECT_CALL(*mDataLoader, start(_)).Times(0);
@@ -755,11 +794,11 @@
     mIncrementalService->deleteStorage(storageId);
 }
 
-TEST_F(IncrementalServiceTest, testDataLoaderDestroyed) {
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(2);
+TEST_F(IncrementalServiceTest, testDataLoaderDestroyedAndDelayed) {
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(6);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
-    EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(2);
-    EXPECT_CALL(*mDataLoader, start(_)).Times(2);
+    EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(6);
+    EXPECT_CALL(*mDataLoader, start(_)).Times(6);
     EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
     EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
     TemporaryDir tempDir;
@@ -768,13 +807,38 @@
                                                IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
     mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {}, {}, {});
+
     // Simulated crash/other connection breakage.
+
+    ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+            .WillByDefault(Invoke(mDataLoaderManager,
+                                  &MockDataLoaderManager::bindToDataLoaderOkWith10sDelay));
+    mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+    ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+            .WillByDefault(Invoke(mDataLoaderManager,
+                                  &MockDataLoaderManager::bindToDataLoaderOkWith100sDelay));
+    mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+    ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+            .WillByDefault(Invoke(mDataLoaderManager,
+                                  &MockDataLoaderManager::bindToDataLoaderOkWith1000sDelay));
+    mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+    ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+            .WillByDefault(Invoke(mDataLoaderManager,
+                                  &MockDataLoaderManager::bindToDataLoaderOkWith10000sDelay));
+    mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+    ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+            .WillByDefault(Invoke(mDataLoaderManager,
+                                  &MockDataLoaderManager::bindToDataLoaderOkWith10000sDelay));
     mDataLoaderManager->setDataLoaderStatusDestroyed();
 }
 
 TEST_F(IncrementalServiceTest, testStartDataLoaderCreate) {
     mDataLoader->initializeCreateOkNoStatus();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoader, start(_)).Times(1);
@@ -793,7 +857,7 @@
 
 TEST_F(IncrementalServiceTest, testStartDataLoaderPendingStart) {
     mDataLoader->initializeCreateOkNoStatus();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoader, start(_)).Times(1);
@@ -811,7 +875,7 @@
 
 TEST_F(IncrementalServiceTest, testStartDataLoaderCreateUnavailable) {
     mDataLoader->initializeCreateOkNoStatus();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoader, start(_)).Times(0);
@@ -827,12 +891,30 @@
     mDataLoaderManager->setDataLoaderStatusUnavailable();
 }
 
+TEST_F(IncrementalServiceTest, testStartDataLoaderCreateUnrecoverable) {
+    mDataLoader->initializeCreateOkNoStatus();
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
+    EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoader, start(_)).Times(0);
+    EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
+    EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+    TemporaryDir tempDir;
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
+    ASSERT_GE(storageId, 0);
+    ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+                                                  {}, {}));
+    mDataLoaderManager->setDataLoaderStatusUnrecoverable();
+}
+
 TEST_F(IncrementalServiceTest, testStartDataLoaderRecreateOnPendingReads) {
     mIncFs->waitForPendingReadsSuccess();
     mIncFs->openMountSuccess();
     mDataLoader->initializeCreateOkNoStatus();
 
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(2);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(2);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(2);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(2);
     EXPECT_CALL(*mDataLoader, start(_)).Times(0);
@@ -856,7 +938,7 @@
 TEST_F(IncrementalServiceTest, testStartDataLoaderUnhealthyStorage) {
     mIncFs->openMountSuccess();
 
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoader, start(_)).Times(1);
@@ -1406,7 +1488,7 @@
 }
 
 TEST_F(IncrementalServiceTest, testPerUidTimeoutsTooShort) {
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoader, start(_)).Times(1);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 97e7582..a0e5c5d 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1291,7 +1291,6 @@
         t.traceBegin("startOtherServices");
 
         final Context context = mSystemContext;
-        VibratorService vibrator = null;
         DynamicSystemService dynamicSystem = null;
         IStorageManager storageManager = null;
         NetworkManagementService networkManagement = null;
@@ -1417,11 +1416,6 @@
             mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class);
             t.traceEnd();
 
-            t.traceBegin("StartVibratorService");
-            vibrator = new VibratorService(context);
-            ServiceManager.addService("vibrator", vibrator);
-            t.traceEnd();
-
             t.traceBegin("StartDynamicSystemService");
             dynamicSystem = new DynamicSystemService(context);
             ServiceManager.addService("dynamic_system", dynamicSystem);
@@ -2490,14 +2484,6 @@
 
         // It is now time to start up the app processes...
 
-        t.traceBegin("MakeVibratorServiceReady");
-        try {
-            vibrator.systemReady();
-        } catch (Throwable e) {
-            reportWtf("making Vibrator Service ready", e);
-        }
-        t.traceEnd();
-
         t.traceBegin("MakeLockSettingsServiceReady");
         if (lockSettings != null) {
             try {
diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
index cbebe69..2219d47 100644
--- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
@@ -38,7 +38,6 @@
 
 import android.annotation.UserIdInt;
 import android.app.Application;
-import android.app.backup.BackupManager.OperationType;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.IBackupObserver;
 import android.app.backup.IFullBackupRestoreObserver;
@@ -874,8 +873,7 @@
                 SecurityException.class,
                 () ->
                         backupManagerService.requestBackup(
-                                mUserTwoId, packages, observer, monitor, 0,
-                                OperationType.BACKUP));
+                                mUserTwoId, packages, observer, monitor, 0));
     }
 
     /**
@@ -893,11 +891,9 @@
         IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class);
         setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ true);
 
-        backupManagerService.requestBackup(mUserTwoId, packages, observer, monitor, /* flags */ 0,
-                OperationType.BACKUP);
+        backupManagerService.requestBackup(mUserTwoId, packages, observer, monitor, /* flags */ 0);
 
-        verify(mUserTwoService).requestBackup(packages, observer, monitor, /* flags */ 0,
-                OperationType.BACKUP);
+        verify(mUserTwoService).requestBackup(packages, observer, monitor, /* flags */ 0);
     }
 
     /** Test that the backup service routes methods correctly to the user that requests it. */
@@ -910,11 +906,9 @@
         IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class);
         setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
 
-        backupManagerService.requestBackup(mUserOneId, packages, observer, monitor, /* flags */ 0,
-                OperationType.BACKUP);
+        backupManagerService.requestBackup(mUserOneId, packages, observer, monitor, /* flags */ 0);
 
-        verify(mUserOneService).requestBackup(packages, observer, monitor, /* flags */ 0,
-                OperationType.BACKUP);
+        verify(mUserOneService).requestBackup(packages, observer, monitor, /* flags */ 0);
     }
 
     /** Test that the backup service routes methods correctly to the user that requests it. */
@@ -927,11 +921,9 @@
         IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class);
         setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
 
-        backupManagerService.requestBackup(mUserTwoId, packages, observer, monitor, /* flags */ 0,
-                OperationType.BACKUP);
+        backupManagerService.requestBackup(mUserTwoId, packages, observer, monitor, /* flags */ 0);
 
-        verify(mUserOneService, never()).requestBackup(packages, observer, monitor, /* flags */ 0,
-                OperationType.BACKUP);
+        verify(mUserOneService, never()).requestBackup(packages, observer, monitor, /* flags */ 0);
     }
 
     /**
@@ -1092,11 +1084,9 @@
         registerUser(backupManagerService, mUserOneId, mUserOneService);
         setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
 
-        backupManagerService.beginRestoreSession(mUserOneId, TEST_PACKAGE, TEST_TRANSPORT,
-                OperationType.BACKUP);
+        backupManagerService.beginRestoreSession(mUserOneId, TEST_PACKAGE, TEST_TRANSPORT);
 
-        verify(mUserOneService).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT,
-                OperationType.BACKUP);
+        verify(mUserOneService).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT);
     }
 
     /** Test that the backup service does not route methods for non-registered users. */
@@ -1106,11 +1096,9 @@
         registerUser(backupManagerService, mUserOneId, mUserOneService);
         setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
 
-        backupManagerService.beginRestoreSession(mUserTwoId, TEST_PACKAGE, TEST_TRANSPORT,
-                OperationType.BACKUP);
+        backupManagerService.beginRestoreSession(mUserTwoId, TEST_PACKAGE, TEST_TRANSPORT);
 
-        verify(mUserOneService, never()).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT,
-                OperationType.BACKUP);
+        verify(mUserOneService, never()).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT);
     }
 
     /** Test that the backup service routes methods correctly to the user that requests it. */
diff --git a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
index f0d7006..da3d1d6 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
@@ -198,6 +198,10 @@
                         return mVibratorProviders.get(vibratorId)
                                 .newVibratorController(vibratorId, listener);
                     }
+
+                    @Override
+                    void addService(String name, IBinder service) {
+                    }
                 });
         service.systemReady();
         return service;
diff --git a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
deleted file mode 100644
index 633957a..0000000
--- a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
+++ /dev/null
@@ -1,757 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-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.atLeastOnce;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.AppOpsManager;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.PackageManagerInternal;
-import android.hardware.input.IInputManager;
-import android.hardware.input.InputManager;
-import android.hardware.vibrator.IVibrator;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IVibratorStateListener;
-import android.os.Looper;
-import android.os.PowerManagerInternal;
-import android.os.PowerSaveState;
-import android.os.Process;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.os.VibratorInfo;
-import android.os.test.TestLooper;
-import android.platform.test.annotations.Presubmit;
-import android.provider.Settings;
-import android.view.InputDevice;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.vibrator.FakeVibrator;
-import com.android.server.vibrator.FakeVibratorControllerProvider;
-import com.android.server.vibrator.VibratorController;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-
-/**
- * Tests for {@link VibratorService}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:VibratorServiceTest
- */
-@Presubmit
-public class VibratorServiceTest {
-
-    private static final int TEST_TIMEOUT_MILLIS = 1_000;
-    private static final int UID = Process.ROOT_UID;
-    private static final int VIBRATOR_ID = 1;
-    private static final String PACKAGE_NAME = "package";
-    private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
-    private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
-            .setBatterySaverEnabled(true).build();
-    private static final VibrationAttributes ALARM_ATTRS =
-            new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
-    private static final VibrationAttributes HAPTIC_FEEDBACK_ATTRS =
-            new VibrationAttributes.Builder().setUsage(
-                    VibrationAttributes.USAGE_TOUCH).build();
-    private static final VibrationAttributes NOTIFICATION_ATTRS =
-            new VibrationAttributes.Builder().setUsage(
-                    VibrationAttributes.USAGE_NOTIFICATION).build();
-    private static final VibrationAttributes RINGTONE_ATTRS =
-            new VibrationAttributes.Builder().setUsage(
-                    VibrationAttributes.USAGE_RINGTONE).build();
-
-    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
-    @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
-
-    @Mock private PackageManagerInternal mPackageManagerInternalMock;
-    @Mock private PowerManagerInternal mPowerManagerInternalMock;
-    @Mock private AppOpsManager mAppOpsManagerMock;
-    @Mock private IVibratorStateListener mVibratorStateListenerMock;
-    @Mock private IInputManager mIInputManagerMock;
-    @Mock private IBinder mVibratorStateListenerBinderMock;
-
-    private TestLooper mTestLooper;
-    private ContextWrapper mContextSpy;
-    private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
-    private FakeVibrator mFakeVibrator;
-    private FakeVibratorControllerProvider mVibratorProvider;
-
-    @Before
-    public void setUp() throws Exception {
-        mTestLooper = new TestLooper();
-        mFakeVibrator = new FakeVibrator();
-        mVibratorProvider = new FakeVibratorControllerProvider(mTestLooper.getLooper());
-        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
-        InputManager inputManager = InputManager.resetInstance(mIInputManagerMock);
-
-        ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
-        when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
-        when(mContextSpy.getSystemService(eq(Context.VIBRATOR_SERVICE))).thenReturn(mFakeVibrator);
-        when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager);
-        when(mContextSpy.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManagerMock);
-        when(mVibratorStateListenerMock.asBinder()).thenReturn(mVibratorStateListenerBinderMock);
-        when(mPackageManagerInternalMock.getSystemUiServiceComponent())
-                .thenReturn(new ComponentName("", ""));
-        doAnswer(invocation -> {
-            mRegisteredPowerModeListener = invocation.getArgument(0);
-            return null;
-        }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any());
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
-
-        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
-
-        addLocalServiceMock(PackageManagerInternal.class, mPackageManagerInternalMock);
-        addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        InputManager.clearInstance();
-        LocalServices.removeServiceForTest(PackageManagerInternal.class);
-        LocalServices.removeServiceForTest(PowerManagerInternal.class);
-    }
-
-    private VibratorService createService() {
-        VibratorService service = new VibratorService(mContextSpy,
-                new VibratorService.Injector() {
-                    @Override
-                    VibratorController createVibratorController(
-                            VibratorController.OnVibrationCompleteListener listener) {
-                        return mVibratorProvider.newVibratorController(VIBRATOR_ID, listener);
-                    }
-
-                    @Override
-                    Handler createHandler(Looper looper) {
-                        return new Handler(mTestLooper.getLooper());
-                    }
-
-                    @Override
-                    void addService(String name, IBinder service) {
-                        // ignore
-                    }
-                });
-        service.systemReady();
-        return service;
-    }
-
-    @Test
-    public void createService_initializesNativeService() {
-        createService();
-        assertTrue(mVibratorProvider.isInitialized());
-    }
-
-    @Test
-    public void hasVibrator_withVibratorHalPresent_returnsTrue() {
-        assertTrue(createService().hasVibrator());
-    }
-
-    @Test
-    public void hasVibrator_withNoVibratorHalPresent_returnsFalse() {
-        mVibratorProvider.disableVibrators();
-        assertFalse(createService().hasVibrator());
-    }
-
-    @Test
-    public void hasAmplitudeControl_withAmplitudeControlSupport_returnsTrue() {
-        mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        assertTrue(createService().hasAmplitudeControl());
-    }
-
-    @Test
-    public void hasAmplitudeControl_withNoAmplitudeControlSupport_returnsFalse() {
-        assertFalse(createService().hasAmplitudeControl());
-    }
-
-    @Test
-    public void hasAmplitudeControl_withInputDevices_returnsTrue() throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
-        mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
-        assertTrue(createService().hasAmplitudeControl());
-    }
-
-    @Test
-    public void getVibratorInfo_returnsSameInfoFromNative() {
-        mVibratorProvider.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS,
-                IVibrator.CAP_AMPLITUDE_CONTROL);
-        mVibratorProvider.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        mVibratorProvider.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK);
-
-        VibratorInfo info = createService().getVibratorInfo();
-        assertTrue(info.hasAmplitudeControl());
-        assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES,
-                info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
-        assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
-                info.isEffectSupported(VibrationEffect.EFFECT_TICK));
-        assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
-        assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK));
-    }
-
-    @Test
-    public void vibrate_withRingtone_usesRingtoneSettings() throws Exception {
-        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
-        setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
-        vibrate(createService(), VibrationEffect.createOneShot(1, 1), RINGTONE_ATTRS);
-
-        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
-        setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1);
-        vibrateAndWait(createService(), VibrationEffect.createOneShot(10, 10), RINGTONE_ATTRS);
-
-        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
-        setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
-        vibrateAndWait(createService(), VibrationEffect.createOneShot(100, 100), RINGTONE_ATTRS);
-
-        List<VibrationEffect> effects = mVibratorProvider.getEffects();
-        assertEquals(2, effects.size());
-        assertEquals(10, effects.get(0).getDuration());
-        assertEquals(100, effects.get(1).getDuration());
-    }
-
-    @Test
-    public void vibrate_withPowerModeChange_usesLowPowerModeState() throws Exception {
-        VibratorService service = createService();
-        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
-        vibrate(service, VibrationEffect.createOneShot(1, 1), HAPTIC_FEEDBACK_ATTRS);
-        vibrateAndWait(service, VibrationEffect.createOneShot(2, 2), RINGTONE_ATTRS);
-
-        mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
-        vibrateAndWait(service, VibrationEffect.createOneShot(3, 3), /* attributes= */ null);
-        vibrateAndWait(service, VibrationEffect.createOneShot(4, 4), NOTIFICATION_ATTRS);
-
-        List<VibrationEffect> effects = mVibratorProvider.getEffects();
-        assertEquals(3, effects.size());
-        assertEquals(2, effects.get(0).getDuration());
-        assertEquals(3, effects.get(1).getDuration());
-        assertEquals(4, effects.get(2).getDuration());
-    }
-
-    @Test
-    public void vibrate_withAudioAttributes_usesOriginalAudioUsageInAppOpsManager() {
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
-        AudioAttributes audioAttributes = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY).build();
-        VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder(
-                audioAttributes, effect).build();
-
-        vibrate(service, effect, vibrationAttributes);
-
-        verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY), anyInt(), anyString());
-    }
-
-    @Test
-    public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() {
-        VibratorService service = createService();
-
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), NOTIFICATION_ATTRS);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), HAPTIC_FEEDBACK_ATTRS);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
-                new VibrationAttributes.Builder().setUsage(
-                        VibrationAttributes.USAGE_COMMUNICATION_REQUEST).build());
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
-                new VibrationAttributes.Builder().setUsage(
-                        VibrationAttributes.USAGE_UNKNOWN).build());
-
-        InOrder inOrderVerifier = inOrder(mAppOpsManagerMock);
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_ALARM), anyInt(), anyString());
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_NOTIFICATION), anyInt(), anyString());
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_NOTIFICATION_RINGTONE), anyInt(), anyString());
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString());
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST),
-                anyInt(), anyString());
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString());
-    }
-
-    @Test
-    public void vibrate_withOneShotAndInputDevices_vibratesInputDevices() throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
-        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.createOneShot(100, 128);
-        vibrate(service, effect, ALARM_ATTRS);
-        verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
-
-        // VibrationThread will start this vibration async, so wait before checking it never played.
-        assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service,
-                /* timeout= */ 20));
-    }
-
-    @Test
-    public void vibrate_withOneShotAndAmplitudeControl_turnsVibratorOnAndSetsAmplitude()
-            throws Exception {
-        mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        VibratorService service = createService();
-
-        vibrateAndWait(service, VibrationEffect.createOneShot(100, 128), ALARM_ATTRS);
-
-        List<VibrationEffect> effects = mVibratorProvider.getEffects();
-        assertEquals(1, effects.size());
-        assertEquals(100, effects.get(0).getDuration());
-        assertEquals(Arrays.asList(128), mVibratorProvider.getAmplitudes());
-    }
-
-    @Test
-    public void vibrate_withOneShotAndNoAmplitudeControl_turnsVibratorOnAndIgnoresAmplitude()
-            throws Exception {
-        VibratorService service = createService();
-        clearInvocations();
-
-        vibrateAndWait(service, VibrationEffect.createOneShot(100, 128), ALARM_ATTRS);
-
-        List<VibrationEffect> effects = mVibratorProvider.getEffects();
-        assertEquals(1, effects.size());
-        assertEquals(100, effects.get(0).getDuration());
-        assertTrue(mVibratorProvider.getAmplitudes().isEmpty());
-    }
-
-    @Test
-    public void vibrate_withPrebaked_performsEffect() throws Exception {
-        mVibratorProvider.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
-        vibrateAndWait(service, effect, ALARM_ATTRS);
-
-        VibrationEffect.Prebaked expectedEffect = new VibrationEffect.Prebaked(
-                VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG);
-        assertEquals(Arrays.asList(expectedEffect), mVibratorProvider.getEffects());
-    }
-
-    @Test
-    public void vibrate_withPrebakedAndInputDevices_vibratesFallbackWaveformOnInputDevices()
-            throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
-        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
-        VibratorService service = createService();
-
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
-        verify(mIInputManagerMock).vibrate(eq(1), any(), any());
-
-        // VibrationThread will start this vibration async, so wait before checking it never played.
-        assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service,
-                /* timeout= */ 20));
-    }
-
-    @Test
-    public void vibrate_enteringLowPowerMode_cancelVibration() throws Exception {
-        VibratorService service = createService();
-
-        mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
-        vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS);
-        assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
-        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
-        assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-    }
-
-    @Test
-    public void vibrate_enteringLowPowerModeAndRingtone_doNotCancelVibration() throws Exception {
-        VibratorService service = createService();
-
-        mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
-        vibrate(service, VibrationEffect.createOneShot(1000, 100), RINGTONE_ATTRS);
-        assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
-        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
-        // Settings callback is async, so wait before checking it never got cancelled.
-        assertFalse(waitUntil(s -> !s.isVibrating(), service, /* timeout= */ 20));
-    }
-
-    @Test
-    public void vibrate_withSettingsChanged_doNotCancelVibration() throws Exception {
-        VibratorService service = createService();
-        vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS);
-        assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
-
-        // FakeSettingsProvider don't support testing triggering ContentObserver yet.
-        service.updateVibrators();
-
-        // Settings callback is async, so wait before checking it never got cancelled.
-        assertFalse(waitUntil(s -> !s.isVibrating(), service, /* timeout= */ 20));
-    }
-
-    @Test
-    public void vibrate_withComposed_performsEffect() throws Exception {
-        mVibratorProvider.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
-                .compose();
-        vibrateAndWait(service, effect, ALARM_ATTRS);
-        assertEquals(Arrays.asList(effect), mVibratorProvider.getEffects());
-    }
-
-    @Test
-    public void vibrate_withComposedAndInputDevices_vibratesInputDevices() throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
-        when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
-        when(mIInputManagerMock.getInputDevice(2)).thenReturn(createInputDeviceWithVibrator(2));
-        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
-                .compose();
-        vibrate(service, effect, ALARM_ATTRS);
-        InOrder inOrderVerifier = inOrder(mIInputManagerMock);
-        inOrderVerifier.verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
-        inOrderVerifier.verify(mIInputManagerMock).vibrate(eq(2), eq(effect), any());
-
-        // VibrationThread will start this vibration async, so wait before checking it never played.
-        assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service,
-                /* timeout= */ 20));
-    }
-
-    @Test
-    public void vibrate_withWaveform_controlsVibratorAmplitudeDuringTotalVibrationTime()
-            throws Exception {
-        mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.createWaveform(
-                new long[]{10, 10, 10}, new int[]{100, 200, 50}, -1);
-        vibrateAndWait(service, effect, ALARM_ATTRS);
-
-        assertEquals(Arrays.asList(100, 200, 50), mVibratorProvider.getAmplitudes());
-        assertEquals(
-                Arrays.asList(VibrationEffect.createOneShot(30, VibrationEffect.DEFAULT_AMPLITUDE)),
-                mVibratorProvider.getEffects());
-    }
-
-    @Test
-    public void vibrate_withWaveformAndInputDevices_vibratesInputDevices() throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
-        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.createWaveform(
-                new long[]{10, 10, 10}, new int[]{100, 200, 50}, -1);
-        vibrate(service, effect, ALARM_ATTRS);
-        verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
-
-        // VibrationThread will start this vibration async, so wait before checking it never played.
-        assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service,
-                /* timeout= */ 20));
-    }
-
-    @Test
-    public void vibrate_withNativeCallbackTriggered_finishesVibration() throws Exception {
-        mVibratorProvider.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        VibratorService service = createService();
-
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
-        assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
-        // Trigger callbacks from controller.
-        mTestLooper.moveTimeForward(50);
-        mTestLooper.dispatchAll();
-        assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-    }
-
-    @Test
-    public void cancelVibrate_withDeviceVibrating_callsOff() throws Exception {
-        VibratorService service = createService();
-
-        vibrate(service, VibrationEffect.createOneShot(100, 100), ALARM_ATTRS);
-        assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
-        service.cancelVibrate(service);
-        assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-    }
-
-    @Test
-    public void registerVibratorStateListener_callbacksAreTriggered() throws Exception {
-        VibratorService service = createService();
-        service.registerVibratorStateListener(mVibratorStateListenerMock);
-
-        vibrateAndWait(service, VibrationEffect.createOneShot(100, 100), ALARM_ATTRS);
-
-        InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock);
-        // First notification done when listener is registered.
-        inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false));
-        inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(true));
-        inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false));
-        inOrderVerifier.verifyNoMoreInteractions();
-    }
-
-    @Test
-    public void unregisterVibratorStateListener_callbackNotTriggeredAfter() throws Exception {
-        VibratorService service = createService();
-
-        service.registerVibratorStateListener(mVibratorStateListenerMock);
-
-        vibrate(service, VibrationEffect.createOneShot(30, 100), ALARM_ATTRS);
-        assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
-        service.unregisterVibratorStateListener(mVibratorStateListenerMock);
-        // Trigger callbacks from controller.
-        mTestLooper.moveTimeForward(50);
-        mTestLooper.dispatchAll();
-        assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
-        InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock);
-        // First notification done when listener is registered.
-        inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false));
-        inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(true));
-        inOrderVerifier.verify(mVibratorStateListenerMock, atLeastOnce()).asBinder(); // unregister
-        inOrderVerifier.verifyNoMoreInteractions();
-    }
-
-    @Test
-    public void scale_withPrebaked_userIntensitySettingAsEffectStrength() throws Exception {
-        // Alarm vibration is always VIBRATION_INTENSITY_HIGH.
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-        mVibratorProvider.setSupportedEffects(
-                VibrationEffect.EFFECT_CLICK,
-                VibrationEffect.EFFECT_TICK,
-                VibrationEffect.EFFECT_DOUBLE_CLICK,
-                VibrationEffect.EFFECT_HEAVY_CLICK);
-        VibratorService service = createService();
-
-        vibrateAndWait(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
-        vibrateAndWait(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
-                NOTIFICATION_ATTRS);
-        vibrateAndWait(service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK),
-                HAPTIC_FEEDBACK_ATTRS);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK), RINGTONE_ATTRS);
-
-        List<Integer> playedStrengths = mVibratorProvider.getEffects().stream()
-                .map(VibrationEffect.Prebaked.class::cast)
-                .map(VibrationEffect.Prebaked::getEffectStrength)
-                .collect(Collectors.toList());
-        assertEquals(Arrays.asList(
-                VibrationEffect.EFFECT_STRENGTH_STRONG,
-                VibrationEffect.EFFECT_STRENGTH_MEDIUM,
-                VibrationEffect.EFFECT_STRENGTH_LIGHT),
-                playedStrengths);
-    }
-
-    @Test
-    public void scale_withOneShotAndWaveform_usesScaleLevelOnAmplitude() throws Exception {
-        mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_HIGH);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-
-        mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        VibratorService service = createService();
-
-        vibrateAndWait(service, VibrationEffect.createOneShot(20, 100), ALARM_ATTRS);
-        vibrateAndWait(service, VibrationEffect.createOneShot(20, 100), NOTIFICATION_ATTRS);
-        vibrateAndWait(service,
-                VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1),
-                HAPTIC_FEEDBACK_ATTRS);
-        vibrate(service, VibrationEffect.createOneShot(20, 255), RINGTONE_ATTRS);
-
-        List<Integer> amplitudes = mVibratorProvider.getAmplitudes();
-        assertEquals(3, amplitudes.size());
-        // Alarm vibration is never scaled.
-        assertEquals(100, amplitudes.get(0).intValue());
-        // Notification vibrations will be scaled with SCALE_VERY_HIGH.
-        assertTrue(amplitudes.get(1) > 150);
-        // Haptic feedback vibrations will be scaled with SCALE_LOW.
-        assertTrue(amplitudes.get(2) < 100 && amplitudes.get(2) > 50);
-    }
-
-    @Test
-    public void scale_withComposed_usesScaleLevelOnPrimitiveScaleValues() throws Exception {
-        mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_HIGH);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-
-        mVibratorProvider.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
-                .compose();
-
-        vibrateAndWait(service, effect, ALARM_ATTRS);
-        vibrateAndWait(service, effect, NOTIFICATION_ATTRS);
-        vibrateAndWait(service, effect, HAPTIC_FEEDBACK_ATTRS);
-        vibrate(service, effect, RINGTONE_ATTRS);
-
-        List<VibrationEffect.Composition.PrimitiveEffect> primitives =
-                mVibratorProvider.getEffects().stream()
-                        .map(VibrationEffect.Composed.class::cast)
-                        .map(VibrationEffect.Composed::getPrimitiveEffects)
-                        .flatMap(List::stream)
-                        .collect(Collectors.toList());
-
-        // Ringtone vibration is off, so only the other 3 are propagated to native.
-        assertEquals(6, primitives.size());
-
-        // Alarm vibration is never scaled.
-        assertEquals(1f, primitives.get(0).scale, /* delta= */ 1e-2);
-        assertEquals(0.5f, primitives.get(1).scale, /* delta= */ 1e-2);
-
-        // Notification vibrations will be scaled with SCALE_VERY_HIGH.
-        assertEquals(1f, primitives.get(2).scale, /* delta= */ 1e-2);
-        assertTrue(0.7 < primitives.get(3).scale);
-
-        // Haptic feedback vibrations will be scaled with SCALE_LOW.
-        assertTrue(0.5 < primitives.get(4).scale);
-        assertTrue(0.5 > primitives.get(5).scale);
-    }
-
-    private void vibrate(VibratorService service, VibrationEffect effect,
-            VibrationAttributes attrs) {
-        service.vibrate(UID, PACKAGE_NAME, effect, attrs, "some reason", service);
-    }
-
-    private void vibrateAndWait(VibratorService service, VibrationEffect effect,
-            VibrationAttributes attrs) throws Exception {
-        CountDownLatch startedCount = new CountDownLatch(1);
-        CountDownLatch finishedCount = new CountDownLatch(1);
-        service.registerVibratorStateListener(new IVibratorStateListener() {
-            @Override
-            public void onVibrating(boolean vibrating) {
-                if (vibrating) {
-                    startedCount.countDown();
-                } else if (startedCount.getCount() == 0) {
-                    finishedCount.countDown();
-                }
-            }
-
-            @Override
-            public IBinder asBinder() {
-                return mock(IBinder.class);
-            }
-        });
-
-        mTestLooper.startAutoDispatch();
-        service.vibrate(UID, PACKAGE_NAME, effect, attrs, "some reason", service);
-        assertTrue(startedCount.await(1, TimeUnit.SECONDS));
-        assertTrue(finishedCount.await(1, TimeUnit.SECONDS));
-        mTestLooper.stopAutoDispatchAndIgnoreExceptions();
-    }
-
-    private InputDevice createInputDeviceWithVibrator(int id) {
-        return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0,
-                null, /* hasVibrator= */ true, false, false, false /* hasSensor */,
-                false /* hasBattery */);
-    }
-
-    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
-        LocalServices.removeServiceForTest(clazz);
-        LocalServices.addService(clazz, mock);
-    }
-
-    private void setRingerMode(int ringerMode) {
-        AudioManager audioManager = mContextSpy.getSystemService(AudioManager.class);
-        audioManager.setRingerModeInternal(ringerMode);
-        assertEquals(ringerMode, audioManager.getRingerModeInternal());
-    }
-
-    private void setUserSetting(String settingName, int value) {
-        Settings.System.putIntForUser(
-                mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
-    }
-
-    private void setGlobalSetting(String settingName, int value) {
-        Settings.Global.putInt(mContextSpy.getContentResolver(), settingName, value);
-    }
-
-    private boolean waitUntil(Predicate<VibratorService> predicate,
-            VibratorService service, long timeout) throws InterruptedException {
-        long timeoutTimestamp = SystemClock.uptimeMillis() + timeout;
-        boolean predicateResult = false;
-        while (!predicateResult && SystemClock.uptimeMillis() < timeoutTimestamp) {
-            Thread.sleep(10);
-            predicateResult = predicate.test(service);
-        }
-        return predicateResult;
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
index fdf5095..a946534 100644
--- a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.server.am;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 
 import android.content.Context;
@@ -30,15 +28,12 @@
 import android.hardware.power.stats.StateResidencyResult;
 import android.power.PowerStatsInternal;
 import android.util.SparseArray;
-import android.util.SparseLongArray;
 
 import androidx.test.InstrumentationRegistry;
 
 import com.android.internal.os.BatteryStatsImpl;
-import com.android.internal.power.MeasuredEnergyArray;
 
 import org.junit.Before;
-import org.junit.Test;
 
 import java.util.concurrent.CompletableFuture;
 
@@ -63,44 +58,6 @@
                 mBatteryStatsImpl);
     }
 
-    @Test
-    public void getEnergyConsumptionData() {
-        SparseLongArray expectSubsystems = new SparseLongArray();
-        // Add some energy consumers used by BatteryExternalStatsWorker.
-        final int displayId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.DISPLAY, 0,
-                "display");
-        mPowerStatsInternal.incrementEnergyConsumption(displayId, 12345);
-        expectSubsystems.put(MeasuredEnergyArray.SUBSYSTEM_DISPLAY, 12345);
-
-        // Add an arbitrary energy consumer unused by BatteryExternalStatsWorker.
-        // Must be changed if '154' ever becomes an EnergyConsumerType used by BESW.
-        final int someId = mPowerStatsInternal.addEnergyConsumer((byte) 154, 0, "some_consumer");
-        mPowerStatsInternal.incrementEnergyConsumption(someId, 34567);
-
-        // Inform BESW that PowerStatsInternal is ready to query
-        mBatteryExternalStatsWorker.systemServicesReady();
-
-        MeasuredEnergyArray energies = mBatteryExternalStatsWorker.getEnergyConsumptionData();
-
-        assertEquals(expectSubsystems.size(), energies.size());
-        final int size = expectSubsystems.size();
-
-        for (int i = 0; i < size; i++) {
-            int subsystem = expectSubsystems.keyAt(i);
-            // find the subsystem in the returned MeasuredEnergyArray
-            int subsystemIndex = -1;
-            for (int j = 0; j < size; j++) {
-                if (subsystem == energies.getSubsystem(i)) {
-                    subsystemIndex = i;
-                    break;
-                }
-            }
-            assertNotEquals("Subsystem " + subsystem + " not found in MeasuredEnergyArray", -1,
-                    subsystemIndex);
-            assertEquals(expectSubsystems.valueAt(i), energies.getEnergy(subsystemIndex));
-        }
-    }
-
     public class TestInjector extends BatteryExternalStatsWorker.Injector {
         public TestInjector(Context context) {
             super(context);
diff --git a/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java b/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
index 67d379a..1efce39 100644
--- a/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
@@ -16,18 +16,23 @@
 
 package com.android.server.am;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static com.android.server.am.MeasuredEnergySnapshot.UNAVAILABLE;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerAttribution;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
+import android.util.SparseArray;
 import android.util.SparseLongArray;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.power.MeasuredEnergyArray;
+import com.android.server.am.MeasuredEnergySnapshot.MeasuredEnergyDeltaData;
 
-import org.junit.Before;
 import org.junit.Test;
 
 /**
@@ -38,134 +43,198 @@
  */
 @SmallTest
 public final class MeasuredEnergySnapshotTest {
-    private static final int NUMBER_SUBSYSTEMS = 3;
-    private static final int SUBSYSTEM_DISPLAY = 0;
-    private static final int SUBSYSTEM_NEVER_USED = 1;
-    private static final int SUBSYSTEM_CATAPULT = 2;
+    private static final EnergyConsumer CONSUMER_DISPLAY = createEnergyConsumer(
+            0, 0, EnergyConsumerType.DISPLAY, "Display");
+    private static final  EnergyConsumer CONSUMER_OTHER_0 = createEnergyConsumer(
+            47, 0, EnergyConsumerType.OTHER, "GPU");
+    private static final  EnergyConsumer CONSUMER_OTHER_1 = createEnergyConsumer(
+            1, 1, EnergyConsumerType.OTHER, "HPU");
+    private static final  EnergyConsumer CONSUMER_OTHER_2 = createEnergyConsumer(
+            436, 2, EnergyConsumerType.OTHER, "IPU");
 
-    private MeasuredEnergySnapshot mSnapshot;
+    private static final SparseArray<EnergyConsumer> ALL_ID_CONSUMER_MAP = createIdToConsumerMap(
+            CONSUMER_DISPLAY, CONSUMER_OTHER_0, CONSUMER_OTHER_1, CONSUMER_OTHER_2);
+    private static final SparseArray<EnergyConsumer> SOME_ID_CONSUMER_MAP = createIdToConsumerMap(
+            CONSUMER_DISPLAY);
 
-    // Basic MeasuredEnergyArray that supports all the subsystems. Out of order on purpose.
-    private final int[] mAllSubsystems =
-            {SUBSYSTEM_DISPLAY, SUBSYSTEM_CATAPULT, SUBSYSTEM_NEVER_USED};
-    // E.g. mAllSubsystems[mSubsystemIndices[SUBSYSTEM_CATAPULT]]=SUBSYSTEM_CATAPULT
-    private final int[] mSubsystemIndices = {0, 2, 1};
-    private final long[] mCurrentSubsystemEnergyUJ = {111, 0, 0};
-    private final MeasuredEnergyArray mOmniEnergyArray = new MeasuredEnergyArray() {
-        @Override
-        public int getSubsystem(int index) {
-            return mAllSubsystems[index];
-        }
-
-        @Override
-        public long getEnergy(int index) {
-            return mCurrentSubsystemEnergyUJ[index];
-        }
-
-        @Override
-        public int size() {
-            return mAllSubsystems.length;
-        }
+    // Elements in each results are purposefully out of order.
+    private static final  EnergyConsumerResult[] RESULTS_0 = new EnergyConsumerResult[] {
+        createEnergyConsumerResult(CONSUMER_OTHER_0.id, 90, new int[] {47, 3}, new long[] {14, 13}),
+        createEnergyConsumerResult(CONSUMER_DISPLAY.id, 14, null, null),
+        createEnergyConsumerResult(CONSUMER_OTHER_1.id, 0, null, null),
+        // No CONSUMER_OTHER_2
     };
-    private final MeasuredEnergyArray mJustDisplayEnergyArray = new MeasuredEnergyArray() {
-        @Override
-        public int getSubsystem(int index) {
-            return mAllSubsystems[0];
-        }
-
-        @Override
-        public long getEnergy(int index) {
-            return mCurrentSubsystemEnergyUJ[0];
-        }
-
-        @Override
-        public int size() {
-            return 1;
-        }
+    private static final  EnergyConsumerResult[] RESULTS_1 = new EnergyConsumerResult[] {
+        createEnergyConsumerResult(CONSUMER_DISPLAY.id, 24, null, null),
+        createEnergyConsumerResult(CONSUMER_OTHER_0.id, 90, new int[] {47, 3}, new long[] {14, 13}),
+        createEnergyConsumerResult(CONSUMER_OTHER_2.id, 12, new int[] {6}, new long[] {10}),
+        createEnergyConsumerResult(CONSUMER_OTHER_1.id, 12_000, null, null),
+    };
+    private static final  EnergyConsumerResult[] RESULTS_2 = new EnergyConsumerResult[] {
+        createEnergyConsumerResult(CONSUMER_DISPLAY.id, 36, null, null),
+        // No CONSUMER_OTHER_0
+        // No CONSUMER_OTHER_1
+        // No CONSUMER_OTHER_2
+    };
+    private static final  EnergyConsumerResult[] RESULTS_3 = new EnergyConsumerResult[] {
+        // No CONSUMER_DISPLAY
+        createEnergyConsumerResult(CONSUMER_OTHER_2.id, 13, new int[] {6}, new long[] {10}),
+        createEnergyConsumerResult(
+                CONSUMER_OTHER_0.id, 190, new int[] {2, 3, 47, 7}, new long[] {9, 18, 14, 6}),
+        createEnergyConsumerResult(CONSUMER_OTHER_1.id, 12_000, null, null),
+    };
+    private static final  EnergyConsumerResult[] RESULTS_4 = new EnergyConsumerResult[] {
+        createEnergyConsumerResult(CONSUMER_DISPLAY.id, 43, null, null),
+        createEnergyConsumerResult(
+                CONSUMER_OTHER_0.id, 290, new int[] {7, 47, 3, 2}, new long[] {6, 14, 18, 11}),
+        // No CONSUMER_OTHER_1
+        createEnergyConsumerResult(CONSUMER_OTHER_2.id, 165, new int[] {6, 47}, new long[] {10, 8}),
     };
 
-    @Before
-    public void setUp() {
-        mSnapshot = new MeasuredEnergySnapshot(NUMBER_SUBSYSTEMS, mOmniEnergyArray);
+    @Test
+    public void testUpdateAndGetDelta_empty() {
+        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP);
+        assertNull(snapshot.updateAndGetDelta(null));
+        assertNull(snapshot.updateAndGetDelta(new EnergyConsumerResult[0]));
     }
 
     @Test
     public void testUpdateAndGetDelta() {
-        SparseLongArray result;
+        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP);
 
-        // Increment DISPLAY by 15
-        incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 15);
-        result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
-        assertEquals(1, result.size());
-        assertEquals(15, result.get(SUBSYSTEM_DISPLAY));
+        // results0
+        MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0);
+        if (delta != null) { // null is fine here. If non-null, it better be uninteresting though.
+            assertEquals(UNAVAILABLE, delta.displayEnergyUJ);
+            assertNull(delta.otherTotalEnergyUJ);
+            assertNull(delta.otherUidEnergiesUJ);
+        }
 
-        // Increment DISPLAY by 7
-        // Increment CATAPULT by 5. But do NOT include (pull) it in the passed in energy array.
-        incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 7);
-        incrementEnergyOfSubsystem(SUBSYSTEM_CATAPULT, 5);
-        result = mSnapshot.updateAndGetDelta(mJustDisplayEnergyArray); // Just pull display.
-        assertEquals(1, result.size());
-        assertEquals(7, result.get(SUBSYSTEM_DISPLAY));
+        // results1
+        delta = snapshot.updateAndGetDelta(RESULTS_1);
+        assertNotNull(delta);
+        assertEquals(24 - 14, delta.displayEnergyUJ);
 
-        // Increment CATAPULT by 64 (in addition to the previous increase of 5)
-        incrementEnergyOfSubsystem(SUBSYSTEM_CATAPULT, 64);
-        result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
-        assertEquals(1, result.size());
-        assertEquals(5 + 64, result.get(SUBSYSTEM_CATAPULT));
+        assertNotNull(delta.otherTotalEnergyUJ);
+        assertEquals(90 - 90, delta.otherTotalEnergyUJ[0]);
+        assertEquals(12_000 - 0, delta.otherTotalEnergyUJ[1]);
+        assertEquals(0, delta.otherTotalEnergyUJ[2]); // First good pull. Treat delta as 0.
 
-        // Do nothing
-        result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
-        assertEquals("0 results should not appear at all", 0, result.size());
+        assertNotNull(delta.otherUidEnergiesUJ);
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[0]); // No change in uid energies
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[1]);
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[2]);
 
-        // Increment DISPLAY by 42
-        incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 42);
-        result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
-        assertEquals(1, result.size());
-        assertEquals(42, result.get(SUBSYSTEM_DISPLAY));
+        // results2
+        delta = snapshot.updateAndGetDelta(RESULTS_2);
+        assertNotNull(delta);
+        assertEquals(36 - 24, delta.displayEnergyUJ);
+        assertNull(delta.otherUidEnergiesUJ);
+        assertNull(delta.otherTotalEnergyUJ);
 
-        // Increment DISPLAY by 106 and CATAPULT by 13
-        incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 106);
-        incrementEnergyOfSubsystem(SUBSYSTEM_CATAPULT, 13);
-        result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
-        assertEquals(2, result.size());
-        assertEquals(106, result.get(SUBSYSTEM_DISPLAY));
-        assertEquals(13, result.get(SUBSYSTEM_CATAPULT));
+        // results3
+        delta = snapshot.updateAndGetDelta(RESULTS_3);
+        assertNotNull(delta);
+        assertEquals(UNAVAILABLE, delta.displayEnergyUJ);
+
+        assertNotNull(delta.otherTotalEnergyUJ);
+        assertEquals(190 - 90, delta.otherTotalEnergyUJ[0]);
+        assertEquals(12_000 - 12_000, delta.otherTotalEnergyUJ[1]);
+        assertEquals(13 - 12, delta.otherTotalEnergyUJ[2]);
+
+        assertNotNull(delta.otherUidEnergiesUJ);
+        assertEquals(3, delta.otherUidEnergiesUJ[0].size());
+        assertEquals(9 - 0, delta.otherUidEnergiesUJ[0].get(2));
+        assertEquals(18 - 13, delta.otherUidEnergiesUJ[0].get(3));
+        assertEquals(6 - 0, delta.otherUidEnergiesUJ[0].get(7));
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[1]);
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[2]);
+
+        // results4
+        delta = snapshot.updateAndGetDelta(RESULTS_4);
+        assertNotNull(delta);
+        assertEquals(43 - 36, delta.displayEnergyUJ);
+
+        assertNotNull(delta.otherTotalEnergyUJ);
+        assertEquals(290 - 190, delta.otherTotalEnergyUJ[0]);
+        assertEquals(0, delta.otherTotalEnergyUJ[1]); // Not present (e.g. missing data)
+        assertEquals(165 - 13, delta.otherTotalEnergyUJ[2]);
+
+        assertNotNull(delta.otherUidEnergiesUJ);
+        assertEquals(1, delta.otherUidEnergiesUJ[0].size());
+        assertEquals(11 - 9, delta.otherUidEnergiesUJ[0].get(2));
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[1]); // Not present
+        assertEquals(1, delta.otherUidEnergiesUJ[2].size());
+        assertEquals(8, delta.otherUidEnergiesUJ[2].get(47));
     }
 
-    private void incrementEnergyOfSubsystem(int subsystem, long energy) {
-        mCurrentSubsystemEnergyUJ[mSubsystemIndices[subsystem]] += energy;
+    /** Test updateAndGetDelta() when the results have consumers absent from idToConsumerMap. */
+    @Test
+    public void testUpdateAndGetDelta_some() {
+        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(SOME_ID_CONSUMER_MAP);
+
+        // results0
+        MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0);
+        if (delta != null) { // null is fine here. If non-null, it better be uninteresting though.
+            assertEquals(UNAVAILABLE, delta.displayEnergyUJ);
+            assertNull(delta.otherTotalEnergyUJ);
+            assertNull(delta.otherUidEnergiesUJ);
+        }
+
+        // results1
+        delta = snapshot.updateAndGetDelta(RESULTS_1);
+        assertNotNull(delta);
+        assertEquals(24 - 14, delta.displayEnergyUJ);
+        assertNull(delta.otherTotalEnergyUJ); // Although in the results, they're not in the idMap
+        assertNull(delta.otherUidEnergiesUJ);
     }
 
     @Test
-    public void testUpdateAndGetDelta_null() {
-        assertNull(mSnapshot.updateAndGetDelta(null));
+    public void testGetNumOtherOrdinals() {
+        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP);
+        assertEquals(3, snapshot.getNumOtherOrdinals());
     }
 
     @Test
-    public void testHasSubsystem() {
-        // Setup MeasuredEnergySnapshot which reported some of the subsystems.
-        final int[] subsystems = {SUBSYSTEM_DISPLAY, SUBSYSTEM_CATAPULT};
-        MeasuredEnergyArray measuredEnergyArray = new MeasuredEnergyArray() {
-            @Override
-            public int getSubsystem(int index) {
-                return subsystems[index];
-            }
+    public void testGetNumOtherOrdinals_none() {
+        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(SOME_ID_CONSUMER_MAP);
+        assertEquals(0, snapshot.getNumOtherOrdinals());
+    }
 
-            @Override
-            public long getEnergy(int index) {
-                return 0; // Irrelevant for this test.
-            }
+    private static EnergyConsumer createEnergyConsumer(int id, int ord, byte type, String name) {
+        final EnergyConsumer ec = new EnergyConsumer();
+        ec.id = id;
+        ec.ordinal = ord;
+        ec.type = type;
+        ec.name = name;
+        return ec;
+    }
 
-            @Override
-            public int size() {
-                return subsystems.length;
-            }
-        };
-        final MeasuredEnergySnapshot snapshot =
-                new MeasuredEnergySnapshot(NUMBER_SUBSYSTEMS, measuredEnergyArray);
+    private static SparseArray<EnergyConsumer> createIdToConsumerMap(EnergyConsumer ... ecs) {
+        final SparseArray<EnergyConsumer> map = new SparseArray<>();
+        for (EnergyConsumer ec : ecs) {
+            map.put(ec.id, ec);
+        }
+        return map;
+    }
 
-        assertTrue(snapshot.hasSubsystem(SUBSYSTEM_DISPLAY));
-        assertTrue(snapshot.hasSubsystem(SUBSYSTEM_CATAPULT));
-        assertFalse(snapshot.hasSubsystem(SUBSYSTEM_NEVER_USED));
+    private static EnergyConsumerResult createEnergyConsumerResult(
+            int id, long energyUWs, int[] uids, long[] uidEnergies) {
+        final EnergyConsumerResult ecr = new EnergyConsumerResult();
+        ecr.id = id;
+        ecr.energyUWs = energyUWs;
+        if (uids != null) {
+            ecr.attribution = new EnergyConsumerAttribution[uids.length];
+            for (int i = 0; i < uids.length; i++) {
+                ecr.attribution[i] = new EnergyConsumerAttribution();
+                ecr.attribution[i].uid = uids[i];
+                ecr.attribution[i].energyUWs = uidEnergies[i];
+            }
+        }
+        return ecr;
+    }
+
+    private void assertNullOrEmpty(SparseLongArray a) {
+        if (a != null) assertEquals("Array should be null or empty", 0, a.size());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
index b98f025..af11fe1 100644
--- a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.when;
 
+import android.app.backup.BackupAgent;
 import android.app.backup.BackupManager.OperationType;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.IBackupObserver;
@@ -30,6 +31,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import com.android.internal.backup.IBackupTransport;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -56,6 +58,7 @@
     @Mock IBackupObserver mBackupObserver;
     @Mock PackageManager mPackageManager;
     @Mock TransportClient mTransportClient;
+    @Mock IBackupTransport mBackupTransport;
     @Mock BackupEligibilityRules mBackupEligibilityRules;
 
 
@@ -132,6 +135,29 @@
         assertThat(params.mBackupEligibilityRules).isEqualTo(mBackupEligibilityRules);
     }
 
+    @Test
+    public void testGetOperationTypeFromTransport_returnsMigrationForMigrationTransport()
+            throws Exception {
+        when(mTransportClient.connectOrThrow(any())).thenReturn(mBackupTransport);
+        when(mBackupTransport.getTransportFlags()).thenReturn(
+                BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER);
+
+        int operationType = mService.getOperationTypeFromTransport(mTransportClient);
+
+        assertThat(operationType).isEqualTo(OperationType.MIGRATION);
+    }
+
+    @Test
+    public void testGetOperationTypeFromTransport_returnsBackupByDefault()
+            throws Exception {
+        when(mTransportClient.connectOrThrow(any())).thenReturn(mBackupTransport);
+        when(mBackupTransport.getTransportFlags()).thenReturn(0);
+
+        int operationType = mService.getOperationTypeFromTransport(mTransportClient);
+
+        assertThat(operationType).isEqualTo(OperationType.BACKUP);
+    }
+
     private static PackageInfo getPackageInfo(String packageName) {
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.applicationInfo = new ApplicationInfo();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
new file mode 100644
index 0000000..b8dfd56
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
+import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
+import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
+import static com.android.server.hdmi.Constants.ADDR_TV;
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiPortInfo;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.test.TestLooper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+
+/** Tests for {@link ActiveSourceAction} */
+@SmallTest
+@RunWith(JUnit4.class)
+public class PowerStatusMonitorActionTest {
+
+    private Context mContextSpy;
+    private HdmiControlService mHdmiControlService;
+    private FakeNativeWrapper mNativeWrapper;
+
+    private TestLooper mTestLooper = new TestLooper();
+    private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+    private int mPhysicalAddress;
+    private HdmiCecLocalDeviceTv mTvDevice;
+
+    @Mock
+    private IPowerManager mIPowerManagerMock;
+    @Mock
+    private IThermalService mIThermalServiceMock;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+
+        PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
+        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
+        when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+        when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+
+        HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
+
+        mHdmiControlService = new HdmiControlService(mContextSpy) {
+            @Override
+            AudioManager getAudioManager() {
+                return new AudioManager() {
+                    @Override
+                    public void setWiredDeviceConnectionState(
+                            int type, int state, String address, String name) {
+                        // Do nothing.
+                    }
+                };
+            }
+
+            @Override
+            void wakeUp() {
+            }
+
+            @Override
+            boolean isPowerStandby() {
+                return false;
+            }
+
+            @Override
+            protected PowerManager getPowerManager() {
+                return powerManager;
+            }
+
+            @Override
+            protected void writeStringSystemProperty(String key, String value) {
+                // do nothing
+            }
+
+            @Override
+            protected HdmiCecConfig getHdmiCecConfig() {
+                return hdmiCecConfig;
+            }
+        };
+
+        Looper looper = mTestLooper.getLooper();
+        mHdmiControlService.setIoLooper(looper);
+        mNativeWrapper = new FakeNativeWrapper();
+        HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
+                this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+        mHdmiControlService.setCecController(hdmiCecController);
+        mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
+        mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
+        mTvDevice = new HdmiCecLocalDeviceTv(mHdmiControlService);
+        mTvDevice.init();
+        mLocalDevices.add(mTvDevice);
+        mTestLooper.dispatchAll();
+        HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[2];
+        hdmiPortInfo[0] =
+                new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false);
+        hdmiPortInfo[1] =
+                new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false);
+        mNativeWrapper.setPortInfo(hdmiPortInfo);
+        mHdmiControlService.initService();
+        mPhysicalAddress = 0x0000;
+        mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mTestLooper.dispatchAll();
+    }
+
+    @Test
+    public void sourceDevice_1_4_updatesPowerState() {
+        sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000);
+
+        PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice);
+        action.start();
+        assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_UNKNOWN);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                ADDR_TV,
+                ADDR_PLAYBACK_1);
+        assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+        reportPowerStatus(ADDR_PLAYBACK_1, false, HdmiControlManager.POWER_STATUS_ON);
+        assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_ON);
+
+        mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(60));
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+        reportPowerStatus(ADDR_PLAYBACK_1, false, HdmiControlManager.POWER_STATUS_STANDBY);
+        assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_STANDBY);
+    }
+
+    private void assertPowerStatus(int logicalAddress, int powerStatus) {
+        HdmiDeviceInfo deviceInfo = mHdmiControlService.getHdmiCecNetwork().getCecDeviceInfo(
+                logicalAddress);
+        assertThat(deviceInfo).isNotNull();
+        assertThat(deviceInfo.getDevicePowerStatus()).isEqualTo(powerStatus);
+    }
+
+    @Test
+    public void sourceDevice_2_0_doesNotUpdatePowerState() {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+        sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000);
+        reportPowerStatus(ADDR_PLAYBACK_1, true, HdmiControlManager.POWER_STATUS_ON);
+        mTestLooper.dispatchAll();
+
+        PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice);
+        action.start();
+
+        assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_ON);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                ADDR_TV,
+                ADDR_PLAYBACK_1);
+
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus);
+
+        mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(60));
+        mTestLooper.dispatchAll();
+
+        assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_ON);
+    }
+
+    @Test
+    public void mixedSourceDevices_localDevice_1_4_updatesAll() {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+        mTestLooper.dispatchAll();
+        sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000);
+        sendMessageFromPlaybackDevice(ADDR_PLAYBACK_2, 0x2000);
+        reportPowerStatus(ADDR_PLAYBACK_2, true, HdmiControlManager.POWER_STATUS_ON);
+
+        assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_UNKNOWN);
+        assertPowerStatus(ADDR_PLAYBACK_2, HdmiControlManager.POWER_STATUS_ON);
+
+        PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice);
+        action.start();
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                ADDR_TV,
+                ADDR_PLAYBACK_1);
+        assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+        HdmiCecMessage giveDevicePowerStatus2 = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                ADDR_TV,
+                ADDR_PLAYBACK_2);
+        assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus2);
+    }
+
+    @Test
+    public void mixedSourceDevices_localDevice_2_0_onlyUpdates_1_4() {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+        mTestLooper.dispatchAll();
+        sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000);
+        sendMessageFromPlaybackDevice(ADDR_PLAYBACK_2, 0x2000);
+        reportPowerStatus(ADDR_PLAYBACK_2, true, HdmiControlManager.POWER_STATUS_ON);
+
+        PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice);
+        action.start();
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                ADDR_TV,
+                ADDR_PLAYBACK_1);
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+        HdmiCecMessage giveDevicePowerStatus2 = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                ADDR_TV,
+                ADDR_PLAYBACK_2);
+
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus2);
+    }
+
+    private void sendMessageFromPlaybackDevice(int logicalAddress, int physicalAddress) {
+        HdmiCecMessage playbackDevice = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                logicalAddress, physicalAddress, HdmiDeviceInfo.DEVICE_PLAYBACK);
+        mNativeWrapper.onCecMessage(playbackDevice);
+        mTestLooper.dispatchAll();
+    }
+
+    private void reportPowerStatus(int logicalAddress, boolean broadcast, int powerStatus) {
+        int destination = broadcast ? ADDR_BROADCAST : ADDR_TV;
+        HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
+                logicalAddress, destination,
+                powerStatus);
+        mNativeWrapper.onCecMessage(reportPowerStatus);
+        mTestLooper.dispatchAll();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
index 25dbc6b..33ea710 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
@@ -45,6 +45,7 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Process;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.platform.test.annotations.Presubmit;
@@ -89,7 +90,8 @@
         MockitoAnnotations.initMocks(this);
         final Context context = InstrumentationRegistry.getTargetContext();
         mUserId = ActivityManager.getCurrentUser();
-        mCommand = new LockSettingsShellCommand(mLockPatternUtils);
+        mCommand = new LockSettingsShellCommand(mLockPatternUtils, context, 0,
+                Process.SHELL_UID);
         when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(true);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
index 22020ad..bc84e35 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
@@ -96,7 +96,7 @@
 
         int[] reasons = new int[] {
                 PackageManagerService.REASON_FIRST_BOOT,
-                PackageManagerService.REASON_BOOT,
+                PackageManagerService.REASON_POST_BOOT,
                 PackageManagerService.REASON_INSTALL,
                 PackageManagerService.REASON_BACKGROUND_DEXOPT,
                 PackageManagerService.REASON_AB_OTA,
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index b28994c..5574836 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -28,7 +28,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
 import android.app.timedetector.GnssTimeSuggestion;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index 6dcfb42..daa1b25 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -27,7 +27,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
 import android.app.timedetector.GnssTimeSuggestion;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index b4fd302..781cfec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1834,7 +1834,7 @@
         mWm.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false /* updateInputWindows */);
     }
 
-    private void performLayout(DisplayContent dc) {
+    static void performLayout(DisplayContent dc) {
         dc.setLayoutNeeded();
         dc.performLayout(true /* initial */, false /* updateImeWindows */);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 3231f8b..8969695 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -67,11 +67,14 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.when;
 
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.view.Gravity;
 import android.view.InputWindowHandle;
 import android.view.InsetsState;
 import android.view.SurfaceControl;
@@ -559,6 +562,46 @@
         assertTrue(window.isVisibleByPolicy());
     }
 
+    @Test
+    public void testCompatOverrideScale() {
+        final float overrideScale = 2; // 0.5x on client side.
+        final CompatModePackages cmp = mWm.mAtmService.mCompatModePackages;
+        spyOn(cmp);
+        doReturn(overrideScale).when(cmp).getCompatScale(anyString(), anyInt());
+        final WindowState w = createWindow(null, TYPE_APPLICATION_OVERLAY, "win");
+        makeWindowVisible(w);
+        w.setRequestedSize(100, 200);
+        w.mAttrs.width = w.mAttrs.height = WindowManager.LayoutParams.WRAP_CONTENT;
+        w.mAttrs.gravity = Gravity.TOP | Gravity.LEFT;
+        DisplayContentTests.performLayout(mDisplayContent);
+
+        // Frame on screen = 100x200. Compat frame on client = 50x100.
+        final Rect unscaledCompatFrame = new Rect(w.getWindowFrames().mCompatFrame);
+        unscaledCompatFrame.scale(overrideScale);
+        assertEquals(w.getWindowFrames().mFrame, unscaledCompatFrame);
+
+        // Surface should apply the scale.
+        w.prepareSurfaces();
+        verify(w.getPendingTransaction()).setMatrix(w.getSurfaceControl(),
+                overrideScale, 0, 0, overrideScale);
+
+        // According to "dp * density / 160 = px", density is scaled and the size in dp is the same.
+        final CompatibilityInfo compatInfo = cmp.compatibilityInfoForPackageLocked(
+                mContext.getApplicationInfo());
+        final Configuration winConfig = w.getConfiguration();
+        final Configuration clientConfig = new Configuration(w.getConfiguration());
+        compatInfo.applyToConfiguration(clientConfig.densityDpi, clientConfig);
+
+        assertEquals(winConfig.screenWidthDp, clientConfig.screenWidthDp);
+        assertEquals(winConfig.screenHeightDp, clientConfig.screenHeightDp);
+        assertEquals(winConfig.smallestScreenWidthDp, clientConfig.smallestScreenWidthDp);
+        assertEquals(winConfig.densityDpi, (int) (clientConfig.densityDpi * overrideScale));
+
+        final Rect unscaledClientBounds = new Rect(clientConfig.windowConfiguration.getBounds());
+        unscaledClientBounds.scale(overrideScale);
+        assertEquals(w.getWindowConfiguration().getBounds(), unscaledClientBounds);
+    }
+
     @UseTestDisplay(addWindows = { W_ABOVE_ACTIVITY, W_NOTIFICATION_SHADE })
     @Test
     public void testRequestDrawIfNeeded() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index eb6c6ed..83b30a9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -337,6 +337,7 @@
 
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
         attrs.setTitle(name);
+        attrs.packageName = "test";
 
         final WindowState w = new WindowState(service, session, iWindow, token, parent,
                 OP_NONE, attrs, VISIBLE, ownerId, userId,
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 3b06fd3..170ed3e 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -2024,7 +2024,7 @@
         boolean isHandover = request.getExtras() != null && request.getExtras().getBoolean(
                 TelecomManager.EXTRA_IS_HANDOVER_CONNECTION, false);
         boolean addSelfManaged = request.getExtras() != null && request.getExtras().getBoolean(
-                PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, false);
+                PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true);
         Log.i(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, "
                         + "isIncoming: %b, isUnknown: %b, isLegacyHandover: %b, isHandover: %b, "
                         + " addSelfManaged: %b", callManagerAccount, callId, request, isIncoming,
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index 99f2e5e..c6757fb 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -646,7 +646,7 @@
             TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
             TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO,
             TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
-            TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE})
+            TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED})
     public @interface OverrideNetworkType {}
 
     /**
diff --git a/telephony/java/android/telephony/TelephonyDisplayInfo.java b/telephony/java/android/telephony/TelephonyDisplayInfo.java
index 1fcb504..5b5570b 100644
--- a/telephony/java/android/telephony/TelephonyDisplayInfo.java
+++ b/telephony/java/android/telephony/TelephonyDisplayInfo.java
@@ -66,9 +66,26 @@
      * {@link TelephonyManager#NETWORK_TYPE_LTE} network and has E-UTRA-NR Dual Connectivity(EN-DC)
      * capability or is currently connected to the secondary
      * {@link TelephonyManager#NETWORK_TYPE_NR} cellular network on millimeter wave bands.
+     * @deprecated Use{@link #OVERRIDE_NETWORK_TYPE_NR_ADVANCED} instead.
      */
+    @Deprecated
     public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4;
 
+    /**
+     * Override network type when the device is connected NR cellular network and the data rate is
+     * higher than the generic 5G date rate.
+     * Including but not limited to
+     * <ul>
+     *   <li>The device is connected to the NR cellular network on millimeter wave bands. </li>
+     *   <li>The device is connected to the specific network which the carrier is using
+     *   proprietary means to provide a faster overall data connection than would be otherwise
+     *   possible. This may include using other bands unique to the carrier, or carrier
+     *   aggregation, for example.</li>
+     * </ul>
+     * One of the use case is that UX can show a different icon, for example, "5G+"
+     */
+    public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 4;
+
     @NetworkType
     private final  int mNetworkType;
 
@@ -169,7 +186,7 @@
             case OVERRIDE_NETWORK_TYPE_LTE_CA: return "LTE_CA";
             case OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO: return "LTE_ADV_PRO";
             case OVERRIDE_NETWORK_TYPE_NR_NSA: return "NR_NSA";
-            case OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE: return "NR_NSA_MMWAVE";
+            case OVERRIDE_NETWORK_TYPE_NR_ADVANCED: return "NR_NSA_MMWAVE";
             default: return "UNKNOWN";
         }
     }
diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
similarity index 64%
rename from tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
rename to tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
index d1d6a26..0f920b3 100644
--- a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
+++ b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
@@ -16,8 +16,11 @@
 
 package com.android.framework.permission.tests;
 
+import android.content.Context;
 import android.os.Binder;
-import android.os.IVibratorService;
+import android.os.CombinedVibrationEffect;
+import android.os.IBinder;
+import android.os.IVibratorManagerService;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -32,27 +35,28 @@
  * Verify that Hardware apis cannot be called without required permissions.
  */
 @SmallTest
-public class VibratorServicePermissionTest extends TestCase {
+public class VibratorManagerServicePermissionTest extends TestCase {
 
-    private IVibratorService mVibratorService;
+    private IVibratorManagerService mVibratorService;
 
     @Override
     protected void setUp() throws Exception {
-        mVibratorService = IVibratorService.Stub.asInterface(
-                ServiceManager.getService("vibrator"));
+        mVibratorService = IVibratorManagerService.Stub.asInterface(
+                ServiceManager.getService(Context.VIBRATOR_MANAGER_SERVICE));
     }
 
     /**
-     * Test that calling {@link android.os.IVibratorService#vibrate(long)} requires permissions.
+     * Test that calling {@link android.os.IVibratorManagerService#vibrate(int, String,
+     * CombinedVibrationEffect, VibrationAttributes, String, IBinder)} requires permissions.
      * <p>Tests permission:
-     *   {@link android.Manifest.permission#VIBRATE}
-     * @throws RemoteException
+     * {@link android.Manifest.permission#VIBRATE}
      */
     public void testVibrate() throws RemoteException {
         try {
-            final VibrationEffect effect =
-                    VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE);
-            final VibrationAttributes attrs = new VibrationAttributes.Builder()
+            CombinedVibrationEffect effect =
+                    CombinedVibrationEffect.createSynced(
+                            VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
+            VibrationAttributes attrs = new VibrationAttributes.Builder()
                     .setUsage(VibrationAttributes.USAGE_ALARM)
                     .build();
             mVibratorService.vibrate(Process.myUid(), null, effect, attrs,
@@ -64,10 +68,10 @@
     }
 
     /**
-     * Test that calling {@link android.os.IVibratorService#cancelVibrate()} requires permissions.
+     * Test that calling {@link android.os.IVibratorManagerService#cancelVibrate(IBinder)} requires
+     * permissions.
      * <p>Tests permission:
-     *   {@link android.Manifest.permission#VIBRATE}
-     * @throws RemoteException
+     * {@link android.Manifest.permission#VIBRATE}
      */
     public void testCancelVibrate() throws RemoteException {
         try {