Merge changes from topic "presubmit-am-5e074e05e4c74163b4f9a025baec3cd0"

* changes:
  [automerge] Add a runtime check to ensure that system server jars are prefetched. 2p: 418ab8212c 2p: 1cab79eff5
  [automerge] Add a runtime check to ensure that system server jars are prefetched. 2p: 418ab8212c
  Add a runtime check to ensure that system server jars are prefetched.
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/PowerSaveModeModifier.java b/apex/jobscheduler/service/java/com/android/server/tare/PowerSaveModeModifier.java
index 764a3a8..4aaa9f4 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/PowerSaveModeModifier.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/PowerSaveModeModifier.java
@@ -17,27 +17,48 @@
 package com.android.server.tare;
 
 import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.PowerManager;
+import android.os.SystemClock;
 import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.Slog;
 
 /** Modifier that makes things more expensive in adaptive and full battery saver are active. */
 class PowerSaveModeModifier extends Modifier {
+    private static final String TAG = "TARE-" + PowerSaveModeModifier.class.getSimpleName();
+    private static final boolean DEBUG = InternalResourceService.DEBUG
+            || Log.isLoggable(TAG, Log.DEBUG);
+
     private final InternalResourceService mIrs;
-    private final PowerManager mPowerManager;
+    private final PowerSaveModeTracker mPowerSaveModeTracker;
 
     PowerSaveModeModifier(@NonNull InternalResourceService irs) {
         super();
         mIrs = irs;
-        mPowerManager = irs.getContext().getSystemService(PowerManager.class);
+        mPowerSaveModeTracker = new PowerSaveModeTracker();
+    }
+
+    @Override
+    public void setup() {
+        mPowerSaveModeTracker.startTracking(mIrs.getContext());
+    }
+
+    @Override
+    public void tearDown() {
+        mPowerSaveModeTracker.stopTracking(mIrs.getContext());
     }
 
     @Override
     long getModifiedCostToProduce(long ctp) {
-        if (mPowerManager.isPowerSaveMode()) {
+        if (mPowerSaveModeTracker.mPowerSaveModeEnabled) {
             return (long) (1.5 * ctp);
         }
         // TODO: get adaptive power save mode
-        if (mPowerManager.isPowerSaveMode()) {
+        if (mPowerSaveModeTracker.mPowerSaveModeEnabled) {
             return (long) (1.25 * ctp);
         }
         return ctp;
@@ -46,6 +67,45 @@
     @Override
     void dump(IndentingPrintWriter pw) {
         pw.print("power save=");
-        pw.println(mPowerManager.isPowerSaveMode());
+        pw.println(mPowerSaveModeTracker.mPowerSaveModeEnabled);
+    }
+
+    // TODO: migrate to relying on PowerSaveState and ServiceType.TARE
+    private final class PowerSaveModeTracker extends BroadcastReceiver {
+        private final PowerManager mPowerManager;
+        private volatile boolean mPowerSaveModeEnabled;
+
+        private PowerSaveModeTracker() {
+            mPowerManager = mIrs.getContext().getSystemService(PowerManager.class);
+        }
+
+        public void startTracking(@NonNull Context context) {
+            final IntentFilter filter = new IntentFilter();
+            filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
+            context.registerReceiver(this, filter);
+
+            // Initialise tracker state.
+            mPowerSaveModeEnabled = mPowerManager.isPowerSaveMode();
+        }
+
+        public void stopTracking(@NonNull Context context) {
+            context.unregisterReceiver(this);
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(action)) {
+                final boolean enabled = mPowerManager.isPowerSaveMode();
+                if (DEBUG) {
+                    Slog.d(TAG, "Power save mode changed to " + enabled
+                            + ", fired @ " + SystemClock.elapsedRealtime());
+                }
+                if (mPowerSaveModeEnabled != enabled) {
+                    mPowerSaveModeEnabled = enabled;
+                    mIrs.onDeviceStateChanged();
+                }
+            }
+        }
     }
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index b843dca..cae6cdc 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -25,6 +25,7 @@
 import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
 import static android.app.usage.UsageStatsManager.REASON_SUB_DEFAULT_APP_UPDATE;
 import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY;
+import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_USER_FLAG_INTERACTION;
 import static android.app.usage.UsageStatsManager.REASON_SUB_MASK;
 import static android.app.usage.UsageStatsManager.REASON_SUB_PREDICTED_RESTORED;
 import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_ACTIVE_TIMEOUT;
@@ -1581,7 +1582,11 @@
                     // Only user force can bypass the delay restriction. If the user forced the
                     // app into the RESTRICTED bucket, then a toast confirming the action
                     // shouldn't be surprising.
-                    if (Build.IS_DEBUGGABLE) {
+                    // Exclude REASON_SUB_FORCED_USER_FLAG_INTERACTION since the RESTRICTED bucket
+                    // isn't directly visible in that flow.
+                    if (Build.IS_DEBUGGABLE
+                            && (reason & REASON_SUB_MASK)
+                            != REASON_SUB_FORCED_USER_FLAG_INTERACTION) {
                         Toast.makeText(mContext,
                                 // Since AppStandbyController sits low in the lock hierarchy,
                                 // make sure not to call out with the lock held.
diff --git a/api/Android.bp b/api/Android.bp
index bbe26b7..8370c10 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -93,7 +93,6 @@
 // Silence reflection warnings. See b/168689341
 metalava_cmd += " -J--add-opens=java.base/java.util=ALL-UNNAMED "
 metalava_cmd += " --quiet --no-banner --format=v2 "
-metalava_cmd += " --hide ChangedThrows "
 
 genrule {
     name: "current-api-xml",
@@ -187,8 +186,10 @@
     cmd: metalava_cmd +
         "--check-compatibility:api:released $(location :android.api.module-lib.latest) " +
         // Note: having "public" be the base of module-lib is not perfect -- it should
-        // ideally be a merged public+system), but this will  help when migrating from
-        // MODULE_LIBS -> public.
+        // ideally be a merged public+system (which metalava is not currently able to generate).
+        // This "base" will help when migrating from MODULE_LIBS -> public, but not when
+        // migrating from MODULE_LIBS -> system (where it needs to instead be listed as
+        // an incompatibility).
         "--check-compatibility:base $(location :frameworks-base-api-current.txt) " +
         "--baseline:compatibility:released $(location :android-incompatibilities.api.module-lib.latest) " +
         "--update-baseline:compatibility:released $(genDir)/updated-baseline.txt " +
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index 9c6402a..738b9cf 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -37,7 +37,6 @@
 #include "idmap2/Idmap.h"
 #include "idmap2/LogInfo.h"
 
-using android::Res_value;
 using ::testing::NotNull;
 
 using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
index 5a1d808..32b3d13 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -29,8 +29,6 @@
 #include "idmap2/LogInfo.h"
 #include "idmap2/ResourceMapping.h"
 
-using android::Res_value;
-
 using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
 
 namespace android::idmap2 {
diff --git a/core/api/current.txt b/core/api/current.txt
index 4c8a494..a67bf76 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -2094,7 +2094,7 @@
     field public static final int accessibilityActionScrollUp = 16908344; // 0x1020038
     field public static final int accessibilityActionSetProgress = 16908349; // 0x102003d
     field public static final int accessibilityActionShowOnScreen = 16908342; // 0x1020036
-    field public static final int accessibilityActionShowSuggestions;
+    field public static final int accessibilityActionShowTextSuggestions;
     field public static final int accessibilityActionShowTooltip = 16908356; // 0x1020044
     field public static final int accessibilityActionSwipeDown;
     field public static final int accessibilityActionSwipeLeft;
@@ -5560,11 +5560,11 @@
 
   public final class GameState implements android.os.Parcelable {
     ctor public GameState(boolean, int);
-    ctor public GameState(boolean, int, @Nullable String, @NonNull android.os.Bundle);
+    ctor public GameState(boolean, int, int, int);
     method public int describeContents();
-    method @Nullable public String getDescription();
-    method @NonNull public android.os.Bundle getMetadata();
+    method public int getLabel();
     method public int getMode();
+    method public int getQuality();
     method public boolean isLoading();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.GameState> CREATOR;
@@ -7704,9 +7704,10 @@
     field public static final String EXTRA_PROVISIONING_WIFI_SECURITY_TYPE = "android.app.extra.PROVISIONING_WIFI_SECURITY_TYPE";
     field public static final String EXTRA_PROVISIONING_WIFI_SSID = "android.app.extra.PROVISIONING_WIFI_SSID";
     field public static final String EXTRA_PROVISIONING_WIFI_USER_CERTIFICATE = "android.app.extra.PROVISIONING_WIFI_USER_CERTIFICATE";
-    field public static final String EXTRA_RESOURCE_ID = "android.app.extra.RESOURCE_ID";
-    field public static final String EXTRA_RESOURCE_TYPE_DRAWABLE = "android.app.extra.RESOURCE_TYPE_DRAWABLE";
-    field public static final String EXTRA_RESOURCE_TYPE_STRING = "android.app.extra.RESOURCE_TYPE_STRING";
+    field public static final String EXTRA_RESOURCE_IDS = "android.app.extra.RESOURCE_IDS";
+    field public static final String EXTRA_RESOURCE_TYPE = "android.app.extra.RESOURCE_TYPE";
+    field public static final int EXTRA_RESOURCE_TYPE_DRAWABLE = 1; // 0x1
+    field public static final int EXTRA_RESOURCE_TYPE_STRING = 2; // 0x2
     field public static final String EXTRA_RESULT_LAUNCH_INTENT = "android.app.extra.RESULT_LAUNCH_INTENT";
     field public static final int FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY = 1; // 0x1
     field public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 2; // 0x2
@@ -17402,7 +17403,7 @@
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> REQUEST_MAX_NUM_OUTPUT_RAW;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> REQUEST_PARTIAL_RESULT_COUNT;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Byte> REQUEST_PIPELINE_MAX_DEPTH;
-    field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE;
+    field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Long> REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Float> SCALER_AVAILABLE_MAX_DIGITAL_ZOOM;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> SCALER_AVAILABLE_ROTATE_AND_CROP_MODES;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> SCALER_AVAILABLE_STREAM_USE_CASES;
@@ -18122,22 +18123,23 @@
   }
 
   public final class DynamicRangeProfiles {
-    ctor public DynamicRangeProfiles(@NonNull int[]);
-    method @NonNull public java.util.Set<java.lang.Integer> getProfileCaptureRequestConstraints(int);
-    method @NonNull public java.util.Set<java.lang.Integer> getSupportedProfiles();
-    field public static final int DOLBY_VISION_10B_HDR_OEM = 64; // 0x40
-    field public static final int DOLBY_VISION_10B_HDR_OEM_PO = 128; // 0x80
-    field public static final int DOLBY_VISION_10B_HDR_REF = 16; // 0x10
-    field public static final int DOLBY_VISION_10B_HDR_REF_PO = 32; // 0x20
-    field public static final int DOLBY_VISION_8B_HDR_OEM = 1024; // 0x400
-    field public static final int DOLBY_VISION_8B_HDR_OEM_PO = 2048; // 0x800
-    field public static final int DOLBY_VISION_8B_HDR_REF = 256; // 0x100
-    field public static final int DOLBY_VISION_8B_HDR_REF_PO = 512; // 0x200
-    field public static final int HDR10 = 4; // 0x4
-    field public static final int HDR10_PLUS = 8; // 0x8
-    field public static final int HLG10 = 2; // 0x2
-    field public static final int PUBLIC_MAX = 4096; // 0x1000
-    field public static final int STANDARD = 1; // 0x1
+    ctor public DynamicRangeProfiles(@NonNull long[]);
+    method @NonNull public java.util.Set<java.lang.Long> getProfileCaptureRequestConstraints(long);
+    method @NonNull public java.util.Set<java.lang.Long> getSupportedProfiles();
+    method public boolean isExtraLatencyPresent(long);
+    field public static final long DOLBY_VISION_10B_HDR_OEM = 64L; // 0x40L
+    field public static final long DOLBY_VISION_10B_HDR_OEM_PO = 128L; // 0x80L
+    field public static final long DOLBY_VISION_10B_HDR_REF = 16L; // 0x10L
+    field public static final long DOLBY_VISION_10B_HDR_REF_PO = 32L; // 0x20L
+    field public static final long DOLBY_VISION_8B_HDR_OEM = 1024L; // 0x400L
+    field public static final long DOLBY_VISION_8B_HDR_OEM_PO = 2048L; // 0x800L
+    field public static final long DOLBY_VISION_8B_HDR_REF = 256L; // 0x100L
+    field public static final long DOLBY_VISION_8B_HDR_REF_PO = 512L; // 0x200L
+    field public static final long HDR10 = 4L; // 0x4L
+    field public static final long HDR10_PLUS = 8L; // 0x8L
+    field public static final long HLG10 = 2L; // 0x2L
+    field public static final long PUBLIC_MAX = 4096L; // 0x1000L
+    field public static final long STANDARD = 1L; // 0x1L
   }
 
   public final class ExtensionSessionConfiguration {
@@ -18244,7 +18246,7 @@
     method @NonNull public static java.util.Collection<android.hardware.camera2.params.OutputConfiguration> createInstancesForMultiResolutionOutput(@NonNull android.hardware.camera2.MultiResolutionImageReader);
     method public int describeContents();
     method public void enableSurfaceSharing();
-    method public int getDynamicRangeProfile();
+    method public long getDynamicRangeProfile();
     method public int getMaxSharedSurfaceCount();
     method public int getMirrorMode();
     method public int getStreamUseCase();
@@ -18254,7 +18256,7 @@
     method public int getTimestampBase();
     method public void removeSensorPixelModeUsed(int);
     method public void removeSurface(@NonNull android.view.Surface);
-    method public void setDynamicRangeProfile(int);
+    method public void setDynamicRangeProfile(long);
     method public void setMirrorMode(int);
     method public void setPhysicalCameraId(@Nullable String);
     method public void setStreamUseCase(int);
@@ -27458,7 +27460,6 @@
     field public static final String CATEGORY_PAYMENT = "payment";
     field public static final String EXTRA_CATEGORY = "category";
     field public static final String EXTRA_SERVICE_COMPONENT = "component";
-    field public static final String EXTRA_USERID = "android.nfc.cardemulation.extra.USERID";
     field public static final int SELECTION_MODE_ALWAYS_ASK = 1; // 0x1
     field public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; // 0x2
     field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0
@@ -30698,7 +30699,7 @@
   public class BaseBundle {
     method public void clear();
     method public boolean containsKey(String);
-    method @Nullable public Object get(String);
+    method @Deprecated @Nullable public Object get(String);
     method public boolean getBoolean(String);
     method public boolean getBoolean(String, boolean);
     method @Nullable public boolean[] getBooleanArray(@Nullable String);
@@ -30940,16 +30941,21 @@
     method public float getFloat(String, float);
     method @Nullable public float[] getFloatArray(@Nullable String);
     method @Nullable public java.util.ArrayList<java.lang.Integer> getIntegerArrayList(@Nullable String);
-    method @Nullable public <T extends android.os.Parcelable> T getParcelable(@Nullable String);
-    method @Nullable public android.os.Parcelable[] getParcelableArray(@Nullable String);
-    method @Nullable public <T extends android.os.Parcelable> java.util.ArrayList<T> getParcelableArrayList(@Nullable String);
-    method @Nullable public java.io.Serializable getSerializable(@Nullable String);
+    method @Deprecated @Nullable public <T extends android.os.Parcelable> T getParcelable(@Nullable String);
+    method @Nullable public <T> T getParcelable(@Nullable String, @NonNull Class<T>);
+    method @Deprecated @Nullable public android.os.Parcelable[] getParcelableArray(@Nullable String);
+    method @Nullable public <T> T[] getParcelableArray(@Nullable String, @NonNull Class<T>);
+    method @Deprecated @Nullable public <T extends android.os.Parcelable> java.util.ArrayList<T> getParcelableArrayList(@Nullable String);
+    method @Nullable public <T> java.util.ArrayList<T> getParcelableArrayList(@Nullable String, @NonNull Class<T>);
+    method @Deprecated @Nullable public java.io.Serializable getSerializable(@Nullable String);
+    method @Nullable public <T extends java.io.Serializable> T getSerializable(@Nullable String, @NonNull Class<T>);
     method public short getShort(String);
     method public short getShort(String, short);
     method @Nullable public short[] getShortArray(@Nullable String);
     method @Nullable public android.util.Size getSize(@Nullable String);
     method @Nullable public android.util.SizeF getSizeF(@Nullable String);
-    method @Nullable public <T extends android.os.Parcelable> android.util.SparseArray<T> getSparseParcelableArray(@Nullable String);
+    method @Deprecated @Nullable public <T extends android.os.Parcelable> android.util.SparseArray<T> getSparseParcelableArray(@Nullable String);
+    method @Nullable public <T> android.util.SparseArray<T> getSparseParcelableArray(@Nullable String, @NonNull Class<T>);
     method @Nullable public java.util.ArrayList<java.lang.String> getStringArrayList(@Nullable String);
     method public boolean hasFileDescriptors();
     method public void putAll(android.os.Bundle);
@@ -31798,7 +31804,7 @@
     method public android.os.PowerManager.WakeLock newWakeLock(int, String);
     method @RequiresPermission(android.Manifest.permission.REBOOT) public void reboot(@Nullable String);
     method public void removeThermalStatusListener(@NonNull android.os.PowerManager.OnThermalStatusChangedListener);
-    field public static final int ACQUIRE_CAUSES_WAKEUP = 268435456; // 0x10000000
+    field @Deprecated public static final int ACQUIRE_CAUSES_WAKEUP = 268435456; // 0x10000000
     field public static final String ACTION_DEVICE_IDLE_MODE_CHANGED = "android.os.action.DEVICE_IDLE_MODE_CHANGED";
     field public static final String ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED = "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED";
     field public static final String ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED = "android.os.action.LOW_POWER_STANDBY_ENABLED_CHANGED";
@@ -35449,8 +35455,8 @@
     field public static final String EXTRA_WIFI_NETWORK_RESULT_LIST = "android.provider.extra.WIFI_NETWORK_RESULT_LIST";
     field public static final String INTENT_CATEGORY_USAGE_ACCESS_CONFIG = "android.intent.category.USAGE_ACCESS_CONFIG";
     field public static final String METADATA_USAGE_ACCESS_REASON = "android.settings.metadata.USAGE_ACCESS_REASON";
-    field public static final String SUPERVISOR_VERIFICATION_SETTING_BIOMETRICS = "supervisor_restricted_biometrics_controller";
-    field public static final String SUPERVISOR_VERIFICATION_SETTING_UNKNOWN = "";
+    field public static final int SUPERVISOR_VERIFICATION_SETTING_BIOMETRICS = 1; // 0x1
+    field public static final int SUPERVISOR_VERIFICATION_SETTING_UNKNOWN = 0; // 0x0
   }
 
   public static final class Settings.Global extends android.provider.Settings.NameValueTable {
@@ -49299,7 +49305,7 @@
     method @NonNull public android.view.SurfaceControl.Transaction setDataSpace(@NonNull android.view.SurfaceControl, int);
     method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int);
     method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int, int);
-    method @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int);
+    method @Deprecated @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int);
     method @NonNull public android.view.SurfaceControl.Transaction setLayer(@NonNull android.view.SurfaceControl, @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) int);
     method @NonNull public android.view.SurfaceControl.Transaction setOpaque(@NonNull android.view.SurfaceControl, boolean);
     method @NonNull public android.view.SurfaceControl.Transaction setPosition(@NonNull android.view.SurfaceControl, float, float);
@@ -51945,7 +51951,7 @@
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_SELECTION;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_TEXT;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SHOW_ON_SCREEN;
-    field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SHOW_SUGGESTIONS;
+    field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SHOW_TEXT_SUGGESTIONS;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SHOW_TOOLTIP;
     field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SWIPE_DOWN;
     field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SWIPE_LEFT;
@@ -53119,7 +53125,7 @@
     field public static final int RESULT_SHOWN = 2; // 0x2
     field public static final int RESULT_UNCHANGED_HIDDEN = 1; // 0x1
     field public static final int RESULT_UNCHANGED_SHOWN = 0; // 0x0
-    field public static final int SHOW_FORCED = 2; // 0x2
+    field @Deprecated public static final int SHOW_FORCED = 2; // 0x2
     field public static final int SHOW_IMPLICIT = 1; // 0x1
   }
 
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 594f46b..7ec239d 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -413,6 +413,7 @@
 
   public class StorageManager {
     method public long computeStorageCacheBytes(@NonNull java.io.File);
+    method @Nullable public String getCloudMediaProvider();
     method public void notifyAppIoBlocked(@NonNull java.util.UUID, int, int, int);
     method public void notifyAppIoResumed(@NonNull java.util.UUID, int, int, int);
     method public void setCloudMediaProvider(@Nullable String);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 07b1290..71178cce 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1045,7 +1045,7 @@
 
   public class WallpaperManager {
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void clearWallpaper(int, int);
-    method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public float getWallpaperDimAmount();
+    method @FloatRange(from=0.0f, to=1.0f) @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public float getWallpaperDimAmount();
     method public void setDisplayOffset(android.os.IBinder, int, int);
     method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponent(android.content.ComponentName);
     method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public void setWallpaperDimAmount(@FloatRange(from=0.0f, to=1.0f) float);
@@ -1104,7 +1104,7 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException;
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void resetDrawables(@NonNull String[]);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void resetStrings(@NonNull String[]);
-    method @RequiresPermission(android.Manifest.permission.SEND_LOST_MODE_LOCATION_UPDATES) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @RequiresPermission(android.Manifest.permission.TRIGGER_LOST_MODE) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied();
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean);
@@ -1701,6 +1701,7 @@
     field public static final String EXTRAINFO_ACTION_BUTTON_IMAGE_PREREGISTERING = "android.app.cloudsearch.ACTION_BUTTON_IMAGE";
     field public static final String EXTRAINFO_ACTION_BUTTON_TEXT_PREREGISTERING = "android.app.cloudsearch.ACTION_BUTTON_TEXT";
     field public static final String EXTRAINFO_APP_BADGES = "android.app.cloudsearch.APP_BADGES";
+    field public static final String EXTRAINFO_APP_CARD_ACTION = "android.app.cloudsearch.APP_CARD_ACTION";
     field public static final String EXTRAINFO_APP_CONTAINS_ADS_DISCLAIMER = "android.app.cloudsearch.APP_CONTAINS_ADS_DISCLAIMER";
     field public static final String EXTRAINFO_APP_CONTAINS_IAP_DISCLAIMER = "android.app.cloudsearch.APP_CONTAINS_IAP_DISCLAIMER";
     field public static final String EXTRAINFO_APP_DEVELOPER_NAME = "android.app.cloudsearch.APP_DEVELOPER_NAME";
@@ -1710,6 +1711,7 @@
     field public static final String EXTRAINFO_APP_REVIEW_COUNT = "android.app.cloudsearch.APP_REVIEW_COUNT";
     field public static final String EXTRAINFO_APP_SIZE_BYTES = "android.app.cloudsearch.APP_SIZE_BYTES";
     field public static final String EXTRAINFO_APP_STAR_RATING = "android.app.cloudsearch.APP_STAR_RATING";
+    field public static final String EXTRAINFO_INSTALL_BUTTON_ACTION = "android.app.cloudsearch.INSTALL_BUTTON_ACTION";
     field public static final String EXTRAINFO_LONG_DESCRIPTION = "android.app.cloudsearch.LONG_DESCRIPTION";
     field public static final String EXTRAINFO_SCREENSHOTS = "android.app.cloudsearch.SCREENSHOTS";
     field public static final String EXTRAINFO_SHORT_DESCRIPTION = "android.app.cloudsearch.SHORT_DESCRIPTION";
@@ -2632,18 +2634,18 @@
     method public int describeContents();
     method @NonNull public float[] getAnchorPointInOutputUvSpace();
     method @NonNull public float[] getAnchorPointInWorldSpace();
-    method public float getCameraOrbitPitchDegrees();
-    method public float getCameraOrbitYawDegrees();
+    method @FloatRange(from=-90.0F, to=90.0f) public float getCameraOrbitPitchDegrees();
+    method @FloatRange(from=-180.0F, to=180.0f) public float getCameraOrbitYawDegrees();
     method public float getDollyDistanceInWorldSpace();
-    method public float getFrustumFarInWorldSpace();
-    method public float getFrustumNearInWorldSpace();
-    method public float getVerticalFovDegrees();
+    method @FloatRange(from=0.0f) public float getFrustumFarInWorldSpace();
+    method @FloatRange(from=0.0f) public float getFrustumNearInWorldSpace();
+    method @FloatRange(from=0.0f, to=180.0f, fromInclusive=false) public float getVerticalFovDegrees();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.wallpapereffectsgeneration.CameraAttributes> CREATOR;
   }
 
   public static final class CameraAttributes.Builder {
-    ctor public CameraAttributes.Builder(@NonNull float[], @NonNull float[]);
+    ctor public CameraAttributes.Builder(@NonNull @Size(3) float[], @NonNull @Size(2) float[]);
     method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes build();
     method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes.Builder setCameraOrbitPitchDegrees(@FloatRange(from=-90.0F, to=90.0f) float);
     method @NonNull public android.app.wallpapereffectsgeneration.CameraAttributes.Builder setCameraOrbitYawDegrees(@FloatRange(from=-180.0F, to=180.0f) float);
@@ -2671,12 +2673,11 @@
     method @NonNull public String getTaskId();
     method @NonNull public java.util.List<android.app.wallpapereffectsgeneration.TexturedMesh> getTexturedMeshes();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field public static final int CINEMATIC_EFFECT_STATUS_ERROR = 2; // 0x2
-    field public static final int CINEMATIC_EFFECT_STATUS_NOT_READY = 3; // 0x3
+    field public static final int CINEMATIC_EFFECT_STATUS_ERROR = 0; // 0x0
+    field public static final int CINEMATIC_EFFECT_STATUS_NOT_READY = 2; // 0x2
     field public static final int CINEMATIC_EFFECT_STATUS_OK = 1; // 0x1
-    field public static final int CINEMATIC_EFFECT_STATUS_PENDING = 4; // 0x4
-    field public static final int CINEMATIC_EFFECT_STATUS_TOO_MANY_REQUESTS = 5; // 0x5
-    field public static final int CINEMATIC_EFFECT_STATUS_UNKNOWN = 0; // 0x0
+    field public static final int CINEMATIC_EFFECT_STATUS_PENDING = 3; // 0x3
+    field public static final int CINEMATIC_EFFECT_STATUS_TOO_MANY_REQUESTS = 4; // 0x4
     field @NonNull public static final android.os.Parcelable.Creator<android.app.wallpapereffectsgeneration.CinematicEffectResponse> CREATOR;
     field public static final int IMAGE_CONTENT_TYPE_LANDSCAPE = 2; // 0x2
     field public static final int IMAGE_CONTENT_TYPE_OTHER = 3; // 0x3
@@ -11992,8 +11993,9 @@
   public abstract class WallpaperEffectsGenerationService extends android.app.Service {
     ctor public WallpaperEffectsGenerationService();
     method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
-    method public abstract void onGenerateCinematicEffect(@NonNull android.app.wallpapereffectsgeneration.CinematicEffectRequest);
+    method @MainThread public abstract void onGenerateCinematicEffect(@NonNull android.app.wallpapereffectsgeneration.CinematicEffectRequest);
     method public final void returnCinematicEffectResponse(@NonNull android.app.wallpapereffectsgeneration.CinematicEffectResponse);
+    field public static final String SERVICE_INTERFACE = "android.service.wallpapereffectsgeneration.WallpaperEffectsGenerationService";
   }
 
 }
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 1b45e88..db375d4 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -99,15 +99,6 @@
     
 
 
-RethrowRemoteException: android.app.WallpaperManager#getWallpaperDimAmount():
-    Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause)
-RethrowRemoteException: android.app.WallpaperManager#getWallpaperDimmingAmount():
-    Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause)
-RethrowRemoteException: android.app.WallpaperManager#setWallpaperDimAmount(float):
-    Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause)
-RethrowRemoteException: android.app.WallpaperManager#setWallpaperDimmingAmount(float):
-    Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause)
-
 
 SamShouldBeLast: android.accounts.AccountManager#addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
     
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 6ebf188..da7f084 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2348,6 +2348,8 @@
 
   public abstract class DreamOverlayService extends android.app.Service {
     ctor public DreamOverlayService();
+    method @Nullable public final CharSequence getDreamLabel();
+    method public final boolean isPreviewMode();
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method public abstract void onStartDream(@NonNull android.view.WindowManager.LayoutParams);
     method public final void requestExit();
@@ -3083,6 +3085,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
     method public boolean hasActiveInputConnection(@Nullable android.view.View);
     method public boolean isInputMethodPickerShown();
+    field public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // 0xcc1a029L
   }
 
 }
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index e31a566..8b3c9fa 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -523,6 +523,8 @@
      * Sets whether events (such as posting a notification) originating from an app after it
      * receives the broadcast while in background should be recorded as responses to the broadcast.
      *
+     * <p> Note that this will only be considered when sending explicit broadcast intents.
+     *
      * @param id ID to be used for the response events corresponding to this broadcast. If the
      *           value is {@code 0} (default), then response events will not be recorded. Otherwise,
      *           they will be recorded with the ID provided.
diff --git a/core/java/android/app/GameState.java b/core/java/android/app/GameState.java
index 979dd34..fe6e554 100644
--- a/core/java/android/app/GameState.java
+++ b/core/java/android/app/GameState.java
@@ -18,8 +18,6 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -88,12 +86,11 @@
     // One of the states listed above.
     private final @GameStateMode int mMode;
 
-    // This is a game specific description. For example can be level or scene name.
-    private final @Nullable String mDescription;
+    // A developer-supplied enum, e.g. to indicate level or scene.
+    private final int mLabel;
 
-    // This contains any other game specific parameters not covered by the fields above. It can be
-    // quality parameter data, settings, or game modes.
-    private final @NonNull Bundle mMetaData;
+    // The developer-supplied enum, e.g. to indicate the current quality level.
+    private final int mQuality;
 
     /**
      * Create a GameState with the specified loading status.
@@ -101,29 +98,28 @@
      * @param mode The game state mode of type @GameStateMode.
      */
     public GameState(boolean isLoading, @GameStateMode int mode) {
-        this(isLoading, mode, null, new Bundle());
+        this(isLoading, mode, -1, -1);
     }
 
     /**
      * Create a GameState with the given state variables.
      * @param isLoading Whether the game is in the loading state.
-     * @param mode The game state mode of type @GameStateMode.
-     * @param description An optional description of the state.
-     * @param metaData Optional metadata.
+     * @param mode The game state mode.
+     * @param label An optional developer-supplied enum e.g. for the current level.
+     * @param quality An optional developer-supplied enum, e.g. for the current quality level.
      */
-    public GameState(boolean isLoading, @GameStateMode int mode, @Nullable String description,
-            @NonNull Bundle metaData) {
+    public GameState(boolean isLoading, @GameStateMode int mode, int label, int quality) {
         mIsLoading = isLoading;
         mMode = mode;
-        mDescription = description;
-        mMetaData = metaData;
+        mLabel = label;
+        mQuality = quality;
     }
 
     private GameState(Parcel in) {
         mIsLoading = in.readBoolean();
         mMode = in.readInt();
-        mDescription = in.readString();
-        mMetaData = in.readBundle();
+        mLabel = in.readInt();
+        mQuality = in.readInt();
     }
 
     /**
@@ -141,17 +137,19 @@
     }
 
     /**
-     * @return The state description, or null if one is not set.
+     * @return The developer-supplied enum, e.g. to indicate level or scene. The default value (if
+     * not supplied) is -1.
      */
-    public @Nullable String getDescription() {
-        return mDescription;
+    public int getLabel() {
+        return mLabel;
     }
 
     /**
-     * @return metadata associated with the state.
+     * @return The developer-supplied enum, e.g. to indicate the current quality level. The default
+     * value (if not suplied) is -1.
      */
-    public @NonNull Bundle getMetadata() {
-        return mMetaData;
+    public int getQuality() {
+        return mQuality;
     }
 
     @Override
@@ -163,8 +161,8 @@
     public void writeToParcel(@NonNull Parcel parcel, int flags) {
         parcel.writeBoolean(mIsLoading);
         parcel.writeInt(mMode);
-        parcel.writeString(mDescription);
-        parcel.writeBundle(mMetaData);
+        parcel.writeInt(mLabel);
+        parcel.writeInt(mQuality);
     }
 
     /**
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 28c273e..167de46 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -211,6 +211,7 @@
      *
      * @hide
      */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT)")
     oneway void setWallpaperDimAmount(float dimAmount);
 
     /**
@@ -219,6 +220,7 @@
      *
      * @hide
      */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT)")
     float getWallpaperDimAmount();
 
     /**
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 77c7c6f..99a523a 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1377,18 +1377,16 @@
                     Slog.wtf(TAG, "App instance already created for package=" + mPackageName
                             + " instance=" + cached);
                 }
-                // TODO Return the cached one, unless it's for the wrong user?
+                // TODO Return the cached one, unles it's for the wrong user?
                 // For now, we just add WTF checks.
             }
         }
 
         Application app = null;
 
-        // Temporarily disable per-process app class to investigate b/185177290
-//        final String myProcessName = Process.myProcessName();
-//        String appClass = mApplicationInfo.getCustomApplicationClassNameForProcess(
-//                myProcessName);
-        String appClass = mApplicationInfo.className;
+        final String myProcessName = Process.myProcessName();
+        String appClass = mApplicationInfo.getCustomApplicationClassNameForProcess(
+                myProcessName);
         if (forceDefaultAppClass || (appClass == null)) {
             appClass = "android.app.Application";
         }
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 0a18588..ea80369 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -2023,7 +2023,7 @@
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT)
-    public float getWallpaperDimAmount() {
+    public @FloatRange (from = 0f, to = 1f) float getWallpaperDimAmount() {
         if (sGlobals.mService == null) {
             Log.w(TAG, "WallpaperService not running");
             throw new RuntimeException(new DeadSystemException());
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 753df3d..4720318 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3611,42 +3611,45 @@
 
     /**
      * Broadcast action: notify system apps (e.g. settings, SysUI, etc) that the device management
-     * resources with IDs {@link #EXTRA_RESOURCE_ID} has been updated, the updated resources can be
+     * resources with IDs {@link #EXTRA_RESOURCE_IDS} has been updated, the updated resources can be
      * retrieved using {@link #getDrawable} and {@code #getString}.
      *
      * <p>This broadcast is sent to registered receivers only.
      *
-     * <p> The following extras will be included to identify the type of resource being updated:
-     * <ul>
-     *     <li>{@link #EXTRA_RESOURCE_TYPE_DRAWABLE} for drawable resources</li>
-     *     <li>{@link #EXTRA_RESOURCE_TYPE_STRING} for string resources</li>
-     * </ul>
+     * <p> {@link #EXTRA_RESOURCE_TYPE} will be included to identify the type of resource being
+     * updated.
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_DEVICE_POLICY_RESOURCE_UPDATED =
             "android.app.action.DEVICE_POLICY_RESOURCE_UPDATED";
 
     /**
-     * A boolean extra for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to indicate that a
-     * resource of type {@link Drawable} is being updated.
+     * An {@code int} extra for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to indicate the type
+     * of the resource being updated, the type can be {@link #EXTRA_RESOURCE_TYPE_DRAWABLE} or
+     * {@link #EXTRA_RESOURCE_TYPE_STRING}
      */
-    public static final String EXTRA_RESOURCE_TYPE_DRAWABLE =
-            "android.app.extra.RESOURCE_TYPE_DRAWABLE";
+    public static final String EXTRA_RESOURCE_TYPE =
+            "android.app.extra.RESOURCE_TYPE";
 
     /**
-     * A boolean extra for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to indicate that a
-     * resource of type {@link String} is being updated.
+     * A {@code int} value for {@link #EXTRA_RESOURCE_TYPE} to indicate that a resource of type
+     * {@link Drawable} is being updated.
      */
-    public static final String EXTRA_RESOURCE_TYPE_STRING =
-            "android.app.extra.RESOURCE_TYPE_STRING";
+    public static final int EXTRA_RESOURCE_TYPE_DRAWABLE = 1;
+
+    /**
+     * A {@code int} value for {@link #EXTRA_RESOURCE_TYPE} to indicate that a resource of type
+     * {@link String} is being updated.
+     */
+    public static final int EXTRA_RESOURCE_TYPE_STRING = 2;
 
     /**
      * An integer array extra for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to indicate which
-     * resource IDs (see {@link DevicePolicyResources.UpdatableDrawableId} and
-     * {@link DevicePolicyResources.UpdatableStringId}) have been updated.
+     * resource IDs (see {@link DevicePolicyResources.Drawables} and
+     * {@link DevicePolicyResources.Strings}) have been updated.
      */
-    public static final String EXTRA_RESOURCE_ID =
-            "android.app.extra.RESOURCE_ID";
+    public static final String EXTRA_RESOURCE_IDS =
+            "android.app.extra.RESOURCE_IDS";
 
     /** @hide */
     @NonNull
@@ -6061,7 +6064,7 @@
      * organization-owned managed profile.
      *
      * <p>The caller must hold the
-     * {@link android.Manifest.permission#SEND_LOST_MODE_LOCATION_UPDATES} permission.
+     * {@link android.Manifest.permission#TRIGGER_LOST_MODE} permission.
      *
      * <p> Not for use by third-party applications.
      *
@@ -6071,7 +6074,7 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.SEND_LOST_MODE_LOCATION_UPDATES)
+    @RequiresPermission(android.Manifest.permission.TRIGGER_LOST_MODE)
     public void sendLostModeLocationUpdate(@NonNull @CallbackExecutor Executor executor,
             @NonNull Consumer<Boolean> callback) {
         throwIfParentInstance("sendLostModeLocationUpdate");
@@ -7913,6 +7916,10 @@
     /**
      * Returns the current runtime nearby notification streaming policy set by the device or profile
      * owner.
+     * <p>
+     * The caller must be the target user's device owner/profile owner or hold the
+     * {@link android.Manifest.permission#READ_NEARBY_STREAMING_POLICY READ_NEARBY_STREAMING_POLICY}
+     * permission.
      */
     @RequiresPermission(
             value = android.Manifest.permission.READ_NEARBY_STREAMING_POLICY,
@@ -7956,6 +7963,10 @@
 
     /**
      * Returns the current runtime nearby app streaming policy set by the device or profile owner.
+     * <p>
+     * The caller must be the target user's device owner/profile owner or hold the
+     * {@link android.Manifest.permission#READ_NEARBY_STREAMING_POLICY READ_NEARBY_STREAMING_POLICY}
+     * permission.
      */
     @RequiresPermission(
             value = android.Manifest.permission.READ_NEARBY_STREAMING_POLICY,
diff --git a/core/java/android/app/cloudsearch/SearchResult.java b/core/java/android/app/cloudsearch/SearchResult.java
index af8adac..1ca01d4 100644
--- a/core/java/android/app/cloudsearch/SearchResult.java
+++ b/core/java/android/app/cloudsearch/SearchResult.java
@@ -71,6 +71,8 @@
             EXTRAINFO_APP_BADGES,
             EXTRAINFO_ACTION_BUTTON_TEXT_PREREGISTERING,
             EXTRAINFO_ACTION_BUTTON_IMAGE_PREREGISTERING,
+            EXTRAINFO_APP_CARD_ACTION,
+            EXTRAINFO_INSTALL_BUTTON_ACTION,
             EXTRAINFO_WEB_URL,
             EXTRAINFO_WEB_ICON})
     public @interface SearchResultExtraInfoKey {}
@@ -119,6 +121,14 @@
     @SuppressLint("IntentName")
     public static final String EXTRAINFO_ACTION_BUTTON_IMAGE_PREREGISTERING =
             "android.app.cloudsearch.ACTION_BUTTON_IMAGE";
+    /** Intent for tapping the app card, PendingIntent expected. */
+    @SuppressLint("IntentName")
+    public static final String EXTRAINFO_APP_CARD_ACTION =
+            "android.app.cloudsearch.APP_CARD_ACTION";
+    /** Intent for tapping the install button, PendingIntent expected. */
+    @SuppressLint("IntentName")
+    public static final String EXTRAINFO_INSTALL_BUTTON_ACTION =
+            "android.app.cloudsearch.INSTALL_BUTTON_ACTION";
     /** Web content's URL, String value expected. */
     public static final String EXTRAINFO_WEB_URL = "android.app.cloudsearch.WEB_URL";
     /** Web content's domain icon, android.graphics.drawable.Icon expected. */
diff --git a/core/java/android/app/wallpapereffectsgeneration/CameraAttributes.java b/core/java/android/app/wallpapereffectsgeneration/CameraAttributes.java
index dfbc7a4..c91ce24 100644
--- a/core/java/android/app/wallpapereffectsgeneration/CameraAttributes.java
+++ b/core/java/android/app/wallpapereffectsgeneration/CameraAttributes.java
@@ -18,6 +18,7 @@
 
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
+import android.annotation.Size;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -117,6 +118,7 @@
     /**
      * Get the camera yaw orbit rotation.
      */
+    @FloatRange(from = -180.0f, to = 180.0f)
     public float getCameraOrbitYawDegrees() {
         return mCameraOrbitYawDegrees;
     }
@@ -124,6 +126,7 @@
     /**
      * Get the camera pitch orbit rotation.
      */
+    @FloatRange(from = -90.0f, to = 90.0f)
     public float getCameraOrbitPitchDegrees() {
         return mCameraOrbitPitchDegrees;
     }
@@ -138,6 +141,7 @@
     /**
      * Get the camera vertical fov degrees.
      */
+    @FloatRange(from = 0.0f, to = 180.0f, fromInclusive = false)
     public float getVerticalFovDegrees() {
         return mVerticalFovDegrees;
     }
@@ -145,6 +149,7 @@
     /**
      * Get the frustum in near plane.
      */
+    @FloatRange(from = 0.0f)
     public float getFrustumNearInWorldSpace() {
         return mFrustumNearInWorldSpace;
     }
@@ -152,6 +157,7 @@
     /**
      * Get the frustum in far plane.
      */
+    @FloatRange(from = 0.0f)
     public float getFrustumFarInWorldSpace() {
         return mFrustumFarInWorldSpace;
     }
@@ -217,8 +223,8 @@
          * @hide
          */
         @SystemApi
-        public Builder(@NonNull float[] anchorPointInWorldSpace,
-                @NonNull float[] anchorPointInOutputUvSpace) {
+        public Builder(@NonNull @Size(3) float[] anchorPointInWorldSpace,
+                @NonNull @Size(2) float[] anchorPointInOutputUvSpace) {
             mAnchorPointInWorldSpace = anchorPointInWorldSpace;
             mAnchorPointInOutputUvSpace = anchorPointInOutputUvSpace;
         }
diff --git a/core/java/android/app/wallpapereffectsgeneration/CinematicEffectResponse.java b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectResponse.java
index 1254794..b1d2b38 100644
--- a/core/java/android/app/wallpapereffectsgeneration/CinematicEffectResponse.java
+++ b/core/java/android/app/wallpapereffectsgeneration/CinematicEffectResponse.java
@@ -39,27 +39,31 @@
 public final class CinematicEffectResponse implements Parcelable {
     /** @hide */
     @IntDef(prefix = {"CINEMATIC_EFFECT_STATUS_"},
-            value = {CINEMATIC_EFFECT_STATUS_UNKNOWN,
+            value = {CINEMATIC_EFFECT_STATUS_ERROR,
                     CINEMATIC_EFFECT_STATUS_OK,
-                    CINEMATIC_EFFECT_STATUS_ERROR,
                     CINEMATIC_EFFECT_STATUS_NOT_READY,
                     CINEMATIC_EFFECT_STATUS_PENDING,
-                    CINEMATIC_EFFECT_STATUS_TOO_MANY_REQUESTS})
+                    CINEMATIC_EFFECT_STATUS_TOO_MANY_REQUESTS
+            })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CinematicEffectStatusCode {}
 
-    /** Cinematic effect generation unknown status. */
-    public static final int CINEMATIC_EFFECT_STATUS_UNKNOWN = 0;
+    /** Cinematic effect generation failure with internal error. */
+    public static final int CINEMATIC_EFFECT_STATUS_ERROR = 0;
+
     /** Cinematic effect generation success. */
     public static final int CINEMATIC_EFFECT_STATUS_OK = 1;
-    /** Cinematic effect generation failure. */
-    public static final int CINEMATIC_EFFECT_STATUS_ERROR = 2;
+
     /** Service not ready for cinematic effect generation. */
-    public static final int CINEMATIC_EFFECT_STATUS_NOT_READY = 3;
-    /** Cienmatic effect generation process is pending. */
-    public static final int CINEMATIC_EFFECT_STATUS_PENDING = 4;
-    /** Too manay requests for server to handle. */
-    public static final int CINEMATIC_EFFECT_STATUS_TOO_MANY_REQUESTS = 5;
+    public static final int CINEMATIC_EFFECT_STATUS_NOT_READY = 2;
+    /**
+     * There is already a task being processed for the same task id.
+     * Client should wait for the response and not send the same request
+     * again.
+     */
+    public static final int CINEMATIC_EFFECT_STATUS_PENDING = 3;
+    /** Too many requests for server to handle. */
+    public static final int CINEMATIC_EFFECT_STATUS_TOO_MANY_REQUESTS = 4;
 
     /** @hide */
     @IntDef(prefix = {"IMAGE_CONTENT_TYPE_"},
@@ -71,13 +75,13 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface ImageContentType {}
 
-    /** Image content unknown. */
+    /** Unable to determine image type. */
     public static final int IMAGE_CONTENT_TYPE_UNKNOWN = 0;
     /** Image content is people portrait. */
     public static final int IMAGE_CONTENT_TYPE_PEOPLE_PORTRAIT = 1;
     /** Image content is landscape. */
     public static final int IMAGE_CONTENT_TYPE_LANDSCAPE = 2;
-    /** Image content is doesn't belong to other types. */
+    /** Image content is not people portrait or landscape. */
     public static final int IMAGE_CONTENT_TYPE_OTHER = 3;
 
 
diff --git a/core/java/android/companion/SystemDataTransferRequest.java b/core/java/android/companion/SystemDataTransferRequest.java
index e3b0369..461b4f5 100644
--- a/core/java/android/companion/SystemDataTransferRequest.java
+++ b/core/java/android/companion/SystemDataTransferRequest.java
@@ -17,7 +17,7 @@
 package android.companion;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.provider.OneTimeUseBuilder;
@@ -38,10 +38,25 @@
     private final List<String> mPermissionSyncPackages;
 
     /**
+     * User Id that the request belongs to.
+     * Populated by the system.
+     *
      * @hide
      */
+    @UserIdInt
+    private int mUserId;
+
+    /**
+     * Whether the request is consented by the user.
+     * Populated by the system.
+     *
+     * @hide
+     */
+    private boolean mUserConsented = false;
+
+    /* @hide */
     public SystemDataTransferRequest(int associationId, boolean syncAllPackages,
-            @Nullable List<String> permissionSyncPackages) {
+            @NonNull List<String> permissionSyncPackages) {
         mAssociationId = associationId;
         mPermissionSyncAllPackages = syncAllPackages;
         mPermissionSyncPackages = permissionSyncPackages;
@@ -61,6 +76,37 @@
         return mPermissionSyncPackages;
     }
 
+    /* @hide */
+    public int getUserId() {
+        return mUserId;
+    }
+
+    /* @hide */
+    public boolean isUserConsented() {
+        return mUserConsented;
+    }
+
+    /* @hide */
+    public void setUserId(@UserIdInt int userId) {
+        mUserId = userId;
+    }
+
+    /* @hide */
+    public void setUserConsented(boolean isUserConsented) {
+        mUserConsented = isUserConsented;
+    }
+
+    /* @hide */
+    @Override
+    public String toString() {
+        return "SystemDataTransferRequest("
+                + "associationId=" + mAssociationId
+                + ", isPermissionSyncAllPackages=" + mPermissionSyncAllPackages
+                + ", permissionSyncPackages=[" + String.join(",", mPermissionSyncPackages) + "]"
+                + ", isUserConsented=" + mUserConsented
+                + ")";
+    }
+
     /**
      * A builder for {@link SystemDataTransferRequest}.
      *
@@ -90,9 +136,8 @@
          * <p>If a system or policy granted or revoked permission is granted or revoked by the user
          * later, the permission will be ignored.</p>
          *
-         * @see #setPermissionSyncPackages(List)
-         *
          * @return the builder
+         * @see #setPermissionSyncPackages(List)
          */
         @NonNull
         public Builder setPermissionSyncAllPackages() {
@@ -104,10 +149,9 @@
          * Set a list of packages to sync permissions. You can optionally call
          * {@link #setPermissionSyncAllPackages()} to sync permissions for all the packages.
          *
-         * @see #setPermissionSyncAllPackages()
-         *
          * @param permissionSyncPackages packages to sync permissions
          * @return builder
+         * @see #setPermissionSyncAllPackages()
          */
         @NonNull
         public Builder setPermissionSyncPackages(@NonNull List<String> permissionSyncPackages) {
@@ -127,6 +171,8 @@
         mAssociationId = in.readInt();
         mPermissionSyncAllPackages = in.readBoolean();
         mPermissionSyncPackages = Arrays.asList(in.createString8Array());
+        mUserId = in.readInt();
+        mUserConsented = in.readBoolean();
     }
 
     @Override
@@ -134,6 +180,8 @@
         dest.writeInt(mAssociationId);
         dest.writeBoolean(mPermissionSyncAllPackages);
         dest.writeString8Array(mPermissionSyncPackages.toArray(new String[0]));
+        dest.writeInt(mUserId);
+        dest.writeBoolean(mUserConsented);
     }
 
     @Override
@@ -141,6 +189,18 @@
         return 0;
     }
 
+    /**
+     * Check if two requests have the same data type.
+     */
+    public boolean hasSameDataType(SystemDataTransferRequest request) {
+        // Check if they are permission sync requests.
+        if (this.isPermissionSyncAllPackages() || !this.getPermissionSyncPackages().isEmpty()) {
+            return request.isPermissionSyncAllPackages() || !request.getPermissionSyncPackages()
+                    .isEmpty();
+        }
+        return false;
+    }
+
     @NonNull
     public static final Creator<SystemDataTransferRequest> CREATOR =
             new Creator<SystemDataTransferRequest>() {
diff --git a/core/java/android/hardware/CameraStreamStats.java b/core/java/android/hardware/CameraStreamStats.java
index 85890c1..823d454 100644
--- a/core/java/android/hardware/CameraStreamStats.java
+++ b/core/java/android/hardware/CameraStreamStats.java
@@ -47,7 +47,7 @@
     private int mHistogramType;
     private float[] mHistogramBins;
     private long[] mHistogramCounts;
-    private int mDynamicRangeProfile;
+    private long mDynamicRangeProfile;
     private int mStreamUseCase;
 
     private static final String TAG = "CameraStreamStats";
@@ -70,7 +70,7 @@
 
     public CameraStreamStats(int width, int height, int format,
             int dataSpace, long usage, long requestCount, long errorCount,
-            int startLatencyMs, int maxHalBuffers, int maxAppBuffers, int dynamicRangeProfile,
+            int startLatencyMs, int maxHalBuffers, int maxAppBuffers, long dynamicRangeProfile,
             int streamUseCase) {
         mWidth = width;
         mHeight = height;
@@ -130,7 +130,7 @@
         dest.writeInt(mHistogramType);
         dest.writeFloatArray(mHistogramBins);
         dest.writeLongArray(mHistogramCounts);
-        dest.writeInt(mDynamicRangeProfile);
+        dest.writeLong(mDynamicRangeProfile);
         dest.writeInt(mStreamUseCase);
     }
 
@@ -148,7 +148,7 @@
         mHistogramType = in.readInt();
         mHistogramBins = in.createFloatArray();
         mHistogramCounts = in.createLongArray();
-        mDynamicRangeProfile = in.readInt();
+        mDynamicRangeProfile = in.readLong();
         mStreamUseCase = in.readInt();
     }
 
@@ -204,7 +204,7 @@
         return mHistogramCounts;
     }
 
-    public int getDynamicRangeProfile() {
+    public long getDynamicRangeProfile() {
         return mDynamicRangeProfile;
     }
 
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 524fe79..7bebe1f 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -2448,8 +2448,8 @@
      * @see #REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_MAX
      * @hide
      */
-    public static final Key<int[]> REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP =
-            new Key<int[]>("android.request.availableDynamicRangeProfilesMap", int[].class);
+    public static final Key<long[]> REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP =
+            new Key<long[]>("android.request.availableDynamicRangeProfilesMap", long[].class);
 
     /**
      * <p>Recommended 10-bit dynamic range profile.</p>
@@ -2464,8 +2464,8 @@
      */
     @PublicKey
     @NonNull
-    public static final Key<Integer> REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE =
-            new Key<Integer>("android.request.recommendedTenBitDynamicRangeProfile", int.class);
+    public static final Key<Long> REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE =
+            new Key<Long>("android.request.recommendedTenBitDynamicRangeProfile", long.class);
 
     /**
      * <p>The list of image formats that are supported by this
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 8f42b1f..73735ed 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -990,7 +990,7 @@
      * <p>Reprocessing with 10-bit output targets on 10-bit capable
      * {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT} devices is
      * not supported. Trying to initialize a repreocessable capture session with one ore more
-     * output configurations set {@link OutputConfiguration#setDynamicRangeProfile(int)} to use
+     * output configurations set {@link OutputConfiguration#setDynamicRangeProfile} to use
      * a 10-bit dynamic range profile {@link android.hardware.camera2.params.DynamicRangeProfiles}
      * will trigger {@link IllegalArgumentException}.</p>
      *
@@ -1179,7 +1179,7 @@
      * @see #createCaptureSessionByOutputConfigurations
      * @see #createReprocessableCaptureSession
      * @see #createConstrainedHighSpeedCaptureSession
-     * @see OutputConfiguration#setDynamicRangeProfile(int)
+     * @see OutputConfiguration#setDynamicRangeProfile
      * @see android.hardware.camera2.params.DynamicRangeProfiles
      */
     public void createCaptureSession(
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 4fb496d..468e604 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -1058,7 +1058,7 @@
     }
 
     private DynamicRangeProfiles getDynamicRangeProfiles() {
-        int[] profileArray = getBase(
+        long[] profileArray = getBase(
                 CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP);
 
         if (profileArray == null) {
diff --git a/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java b/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java
index 5c1a4aa..cbd84e7 100644
--- a/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java
+++ b/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java
@@ -16,7 +16,7 @@
 
 package android.hardware.camera2.params;
 
-import android.annotation.IntDef;
+import android.annotation.LongDef;
 import android.annotation.NonNull;
 
 import android.hardware.camera2.CameraMetadata;
@@ -27,7 +27,6 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -44,20 +43,20 @@
  *
  * <p>Some devices may not be able to support 8-bit and/or 10-bit output with different dynamic
  * range profiles within the same capture request. Such device specific constraints can be queried
- * by calling {@link #getProfileCaptureRequestConstraints(int)}. Do note that unsupported
+ * by calling {@link #getProfileCaptureRequestConstraints}. Do note that unsupported
  * combinations will result in {@link IllegalArgumentException} when trying to submit a capture
  * request. Capture requests that only reference outputs configured using the same dynamic range
  * profile value will never fail due to such constraints.</p>
  *
- * @see OutputConfiguration#setDynamicRangeProfile(int)
+ * @see OutputConfiguration#setDynamicRangeProfile
  */
 public final class DynamicRangeProfiles {
     /**
      * This the default 8-bit standard profile that will be used in case where camera clients do not
      * explicitly configure a supported dynamic range profile by calling
-     * {@link OutputConfiguration#setDynamicRangeProfile(int)}.
+     * {@link OutputConfiguration#setDynamicRangeProfile}.
      */
-    public static final int STANDARD =
+    public static final long STANDARD =
             CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_STANDARD;
 
     /**
@@ -65,7 +64,7 @@
      *
      * <p>All 10-bit output capable devices are required to support this profile.</p>
      */
-    public static final int HLG10  =
+    public static final long HLG10  =
             CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HLG10;
 
     /**
@@ -74,7 +73,7 @@
      * <p>This profile utilizes internal static metadata to increase the quality
      * of the capture.</p>
      */
-    public static final int HDR10  =
+    public static final long HDR10  =
             CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HDR10;
 
     /**
@@ -83,7 +82,7 @@
      * <p>In contrast to HDR10, this profile uses internal per-frame metadata
      * to further enhance the quality of the capture.</p>
      */
-    public static final int HDR10_PLUS =
+    public static final long HDR10_PLUS =
             CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_HDR10_PLUS;
 
     /**
@@ -91,13 +90,13 @@
      * accurate capture. This would typically differ from what a specific device
      * might want to tune for a consumer optimized Dolby Vision general capture.</p>
      */
-    public static final int DOLBY_VISION_10B_HDR_REF =
+    public static final long DOLBY_VISION_10B_HDR_REF =
             CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_REF;
 
     /**
      * <p>This is the power optimized mode for 10-bit Dolby Vision HDR Reference Mode.</p>
      */
-    public static final int DOLBY_VISION_10B_HDR_REF_PO =
+    public static final long DOLBY_VISION_10B_HDR_REF_PO =
             CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_REF_PO;
 
     /**
@@ -107,52 +106,52 @@
      * that each specific device would have a different look for their default
      * Dolby Vision capture.</p>
      */
-    public static final int DOLBY_VISION_10B_HDR_OEM =
+    public static final long DOLBY_VISION_10B_HDR_OEM =
             CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_OEM;
 
     /**
      * <p>This is the power optimized mode for 10-bit Dolby Vision HDR device specific capture
      * Mode.</p>
      */
-    public static final int DOLBY_VISION_10B_HDR_OEM_PO =
+    public static final long DOLBY_VISION_10B_HDR_OEM_PO =
             CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_10B_HDR_OEM_PO;
 
     /**
      * <p>This is the 8-bit version of the Dolby Vision reference capture mode optimized
      * for scene accuracy.</p>
      */
-    public static final int DOLBY_VISION_8B_HDR_REF =
+    public static final long DOLBY_VISION_8B_HDR_REF =
             CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_REF;
 
     /**
      * <p>This is the power optimized mode for 8-bit Dolby Vision HDR Reference Mode.</p>
      */
-    public static final int DOLBY_VISION_8B_HDR_REF_PO =
+    public static final long DOLBY_VISION_8B_HDR_REF_PO =
             CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_REF_PO;
 
     /**
      * <p>This is the 8-bit version of device specific tuned and optimized Dolby Vision
      * capture mode.</p>
      */
-    public static final int DOLBY_VISION_8B_HDR_OEM =
+    public static final long DOLBY_VISION_8B_HDR_OEM =
             CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_OEM;
 
     /**
      * <p>This is the power optimized mode for 8-bit Dolby Vision HDR device specific
      * capture Mode.</p>
      */
-    public static final int DOLBY_VISION_8B_HDR_OEM_PO =
+    public static final long DOLBY_VISION_8B_HDR_OEM_PO =
             CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_DOLBY_VISION_8B_HDR_OEM_PO;
 
     /*
      * @hide
      */
-    public static final int PUBLIC_MAX =
+    public static final long PUBLIC_MAX =
             CameraMetadata.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP_MAX;
 
      /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = {"PROFILE_"}, value =
+    @LongDef(prefix = {"PROFILE_"}, value =
             {STANDARD,
              HLG10,
              HDR10,
@@ -168,7 +167,8 @@
     public @interface Profile {
     }
 
-    private final HashMap<Integer, Set<Integer>> mProfileMap = new HashMap<>();
+    private final HashMap<Long, Set<Long>> mProfileMap = new HashMap<>();
+    private final HashMap<Long, Boolean> mLookahedLatencyMap = new HashMap<>();
 
     /**
      * Create a new immutable DynamicRangeProfiles instance.
@@ -193,23 +193,23 @@
      *            if {@code elements} is {@code null}
      *
      */
-    public DynamicRangeProfiles(@NonNull final int[] elements) {
-        if ((elements.length % 2) != 0) {
+    public DynamicRangeProfiles(@NonNull final long[] elements) {
+        if ((elements.length % 3) != 0) {
             throw new IllegalArgumentException("Dynamic range profile map length " +
                     elements.length + " is not even!");
         }
 
-        for (int i = 0; i < elements.length; i += 2) {
+        for (int i = 0; i < elements.length; i += 3) {
             checkProfileValue(elements[i]);
             // STANDARD is not expected to be included
             if (elements[i] == STANDARD) {
                 throw new IllegalArgumentException("Dynamic range profile map must not include a"
                         + " STANDARD profile entry!");
             }
-            HashSet<Integer> profiles = new HashSet<>();
+            HashSet<Long> profiles = new HashSet<>();
 
             if (elements[i+1] != 0) {
-                for (int profile = STANDARD; profile < PUBLIC_MAX; profile <<= 1) {
+                for (long profile = STANDARD; profile < PUBLIC_MAX; profile <<= 1) {
                     if ((elements[i+1] & profile) != 0) {
                         profiles.add(profile);
                     }
@@ -217,12 +217,13 @@
             }
 
             mProfileMap.put(elements[i], profiles);
+            mLookahedLatencyMap.put(elements[i], elements[i+2] != 0L);
         }
 
         // Build the STANDARD constraints depending on the advertised 10-bit limitations
-        HashSet<Integer> standardConstraints = new HashSet<>();
+        HashSet<Long> standardConstraints = new HashSet<>();
         standardConstraints.add(STANDARD);
-        for(Integer profile : mProfileMap.keySet()) {
+        for(Long profile : mProfileMap.keySet()) {
             if (mProfileMap.get(profile).isEmpty() || mProfileMap.get(profile).contains(STANDARD)) {
                 standardConstraints.add(profile);
             }
@@ -235,24 +236,15 @@
     /**
      * @hide
      */
-    public static void checkProfileValue(int profile) {
-        switch (profile) {
-            case STANDARD:
-            case HLG10:
-            case HDR10:
-            case HDR10_PLUS:
-            case DOLBY_VISION_10B_HDR_REF:
-            case DOLBY_VISION_10B_HDR_REF_PO:
-            case DOLBY_VISION_10B_HDR_OEM:
-            case DOLBY_VISION_10B_HDR_OEM_PO:
-            case DOLBY_VISION_8B_HDR_REF:
-            case DOLBY_VISION_8B_HDR_REF_PO:
-            case DOLBY_VISION_8B_HDR_OEM:
-            case DOLBY_VISION_8B_HDR_OEM_PO:
-                //No-op
-                break;
-            default:
-                throw new IllegalArgumentException("Unknown profile " + profile);
+    public static void checkProfileValue(long profile) {
+        if (profile == STANDARD || profile == HLG10 || profile == HDR10 || profile == HDR10_PLUS
+                || profile == DOLBY_VISION_10B_HDR_REF || profile == DOLBY_VISION_10B_HDR_REF_PO
+                || profile == DOLBY_VISION_10B_HDR_OEM || profile == DOLBY_VISION_10B_HDR_OEM_PO
+                || profile == DOLBY_VISION_8B_HDR_REF || profile == DOLBY_VISION_8B_HDR_REF_PO
+                || profile == DOLBY_VISION_8B_HDR_OEM
+                || profile == DOLBY_VISION_8B_HDR_OEM_PO) {//No-op
+        } else {
+            throw new IllegalArgumentException("Unknown profile " + profile);
         }
     }
 
@@ -261,7 +253,7 @@
      *
      * @return non-modifiable set of dynamic range profiles
      */
-     public @NonNull Set<Integer> getSupportedProfiles() {
+     public @NonNull Set<Long> getSupportedProfiles() {
          return Collections.unmodifiableSet(mProfileMap.keySet());
      }
 
@@ -272,7 +264,7 @@
      *
      * <p>For example if assume that a particular 10-bit output capable device
      * returns ({@link #STANDARD}, {@link #HLG10}, {@link #HDR10}) as result from calling
-     * {@link #getSupportedProfiles()} and {@link #getProfileCaptureRequestConstraints(int)}
+     * {@link #getSupportedProfiles()} and {@link #getProfileCaptureRequestConstraints}
      * returns ({@link #STANDARD}, {@link #HLG10}) when given an argument of {@link #STANDARD}.
      * This means that the corresponding camera device will only accept and process capture requests
      * that reference outputs configured using {@link #HDR10} dynamic profile or alternatively
@@ -288,14 +280,40 @@
      *                                    within the list returned by
      *                                    getSupportedProfiles()
      *
-     * @see OutputConfiguration#setDynamicRangeProfile(int)
+     * @see OutputConfiguration#setDynamicRangeProfile
      */
-     public @NonNull Set<Integer> getProfileCaptureRequestConstraints(@Profile int profile) {
-         Set<Integer> ret = mProfileMap.get(profile);
+     public @NonNull Set<Long> getProfileCaptureRequestConstraints(@Profile long profile) {
+         Set<Long> ret = mProfileMap.get(profile);
          if (ret == null) {
              throw new IllegalArgumentException("Unsupported profile!");
          }
 
          return Collections.unmodifiableSet(ret);
      }
+
+    /**
+     * Check whether a given dynamic range profile is suitable for latency sensitive use cases.
+     *
+     * <p>Due to internal lookahead logic, camera outputs configured with some dynamic range
+     * profiles may experience additional latency greater than 3 buffers. Using camera outputs
+     * with such profiles for latency sensitive use cases such as camera preview is not
+     * recommended. Profiles that have such extra streaming delay are typically utilized for
+     * scenarios such as offscreen video recording.</p>
+     *
+     * @return true if the given profile is not suitable for latency sensitive use cases, false
+     *         otherwise
+     * @throws IllegalArgumentException - If the profile argument is not
+     *                                    within the list returned by
+     *                                    getSupportedProfiles()
+     *
+     * @see OutputConfiguration#setDynamicRangeProfile
+     */
+    public boolean isExtraLatencyPresent(@Profile long profile) {
+        Boolean ret = mLookahedLatencyMap.get(profile);
+        if (ret == null) {
+            throw new IllegalArgumentException("Unsupported profile!");
+        }
+
+        return ret;
+    }
 }
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index 8c0dcfc..465abfb 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -246,7 +246,7 @@
          * @return true if stream is able to output 10-bit pixels
          *
          * @see android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT
-         * @see OutputConfiguration#setDynamicRangeProfile(int)
+         * @see OutputConfiguration#setDynamicRangeProfile
          */
         public boolean is10BitCapable() {
             return mIs10BitCapable;
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 8093764..2350b7c 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -421,7 +421,7 @@
      * {@link android.media.MediaCodec} etc.)
      * or {@link ImageFormat#YCBCR_P010}.</p>
      */
-    public void setDynamicRangeProfile(@Profile int profile) {
+    public void setDynamicRangeProfile(@Profile long profile) {
         mDynamicRangeProfile = profile;
     }
 
@@ -430,7 +430,7 @@
      *
      * @return the currently set dynamic range profile
      */
-    public @Profile int getDynamicRangeProfile() {
+    public @Profile long getDynamicRangeProfile() {
         return mDynamicRangeProfile;
     }
 
@@ -1070,7 +1070,7 @@
         int streamUseCase = source.readInt();
 
         checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant");
-        int dynamicRangeProfile = source.readInt();
+        long dynamicRangeProfile = source.readLong();
         DynamicRangeProfiles.checkProfileValue(dynamicRangeProfile);
 
         int timestampBase = source.readInt();
@@ -1217,7 +1217,7 @@
         dest.writeInt(mIsMultiResolution ? 1 : 0);
         // writeList doesn't seem to work well with Integer list.
         dest.writeIntArray(convertIntegerToIntList(mSensorPixelModesUsed));
-        dest.writeInt(mDynamicRangeProfile);
+        dest.writeLong(mDynamicRangeProfile);
         dest.writeInt(mStreamUseCase);
         dest.writeInt(mTimestampBase);
         dest.writeInt(mMirrorMode);
@@ -1335,7 +1335,7 @@
     // The sensor pixel modes that this OutputConfiguration will use
     private ArrayList<Integer> mSensorPixelModesUsed;
     // Dynamic range profile
-    private int mDynamicRangeProfile;
+    private long mDynamicRangeProfile;
     // Stream use case
     private int mStreamUseCase;
     // Timestamp base
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 2fd79cf..c38a847 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1079,16 +1079,24 @@
     }
 
     /**
-     * Gets the key code produced by the specified location on a US keyboard layout.
-     * Key code as defined in {@link android.view.KeyEvent}.
-     * This API is only functional for devices with {@link InputDevice#SOURCE_KEYBOARD} available
-     * which can alter their key mapping using country specific keyboard layouts.
+     * Gets the {@link android.view.KeyEvent key code} produced by the given location on a reference
+     * QWERTY keyboard layout.
+     * <p>
+     * This API is useful for querying the physical location of keys that change the character
+     * produced based on the current locale and keyboard layout.
+     * <p>
+     * @see InputDevice#getKeyCodeForKeyLocation(int) for examples.
      *
-     * @param deviceId The input device id.
-     * @param locationKeyCode The location of a key on a US keyboard layout.
-     * @return The key code produced when pressing the key at the specified location, given the
-     *         active keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the requested
-     *         mapping could not be determined, or if an error occurred.
+     * @param locationKeyCode The location of a key specified as a key code on the QWERTY layout.
+     * This provides a consistent way of referring to the physical location of a key independently
+     * of the current keyboard layout. Also see the
+     * <a href="https://www.w3.org/TR/2017/CR-uievents-code-20170601/#key-alphanumeric-writing-system">
+     * hypothetical keyboard</a> provided by the W3C, which may be helpful for identifying the
+     * physical location of a key.
+     * @return The key code produced by the key at the specified location, given the current
+     * keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the device does not specify
+     * {@link InputDevice#SOURCE_KEYBOARD} or the requested mapping cannot be determined.
+     *
      * @hide
      */
     public int getKeyCodeForKeyLocation(int deviceId, int locationKeyCode) {
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index 0a9fe90..9a780c8 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -84,13 +84,6 @@
     public static final String EXTRA_SERVICE_COMPONENT = "component";
 
     /**
-     * The caller userId extra for {@link #ACTION_CHANGE_DEFAULT}.
-     *
-     * @see #ACTION_CHANGE_DEFAULT
-     */
-    public static final String EXTRA_USERID = "android.nfc.cardemulation.extra.USERID";
-
-    /**
      * Category used for NFC payment services.
      */
     public static final String CATEGORY_PAYMENT = "payment";
diff --git a/core/java/android/os/BadTypeParcelableException.java b/core/java/android/os/BadTypeParcelableException.java
new file mode 100644
index 0000000..2ca3bd2
--- /dev/null
+++ b/core/java/android/os/BadTypeParcelableException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/** Used by Parcel to signal that the type on the payload was not expected by the caller. */
+class BadTypeParcelableException extends BadParcelableException {
+    BadTypeParcelableException(String msg) {
+        super(msg);
+    }
+    BadTypeParcelableException(Exception cause) {
+        super(cause);
+    }
+    BadTypeParcelableException(String msg, Throwable cause) {
+        super(msg, cause);
+    }
+}
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 244335d..45812e5 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -16,6 +16,8 @@
 
 package android.os;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -31,7 +33,7 @@
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Set;
-import java.util.function.Function;
+import java.util.function.BiFunction;
 
 /**
  * A mapping from String keys to values of various types. In most cases, you
@@ -254,8 +256,8 @@
         }
         try {
             return getValueAt(0, String.class);
-        } catch (ClassCastException | BadParcelableException e) {
-            typeWarning("getPairValue()", /* value */ null, "String", e);
+        } catch (ClassCastException | BadTypeParcelableException e) {
+            typeWarning("getPairValue()", "String", e);
             return null;
         }
     }
@@ -320,28 +322,46 @@
      * This call should always be made after {@link #unparcel()} or inside a lock after making sure
      * {@code mMap} is not null.
      *
+     * @deprecated Use {@link #getValue(String, Class, Class[])}. This method should only be used in
+     *      other deprecated APIs.
+     *
      * @hide
      */
+    @Deprecated
+    @Nullable
     final Object getValue(String key) {
         return getValue(key, /* clazz */ null);
     }
 
+    /** Same as {@link #getValue(String, Class, Class[])} with no item types. */
+    @Nullable
+    final <T> T getValue(String key, @Nullable Class<T> clazz) {
+        // Avoids allocating Class[0] array
+        return getValue(key, clazz, (Class<?>[]) null);
+    }
+
     /**
-     * Returns the value for key {@code key} for expected return type {@param clazz} (or {@code
+     * Returns the value for key {@code key} for expected return type {@code clazz} (or pass {@code
      * null} for no type check).
      *
+     * For {@code itemTypes}, see {@link Parcel#readValue(int, ClassLoader, Class, Class[])}.
+     *
      * This call should always be made after {@link #unparcel()} or inside a lock after making sure
      * {@code mMap} is not null.
      *
      * @hide
      */
-    final <T> T getValue(String key, @Nullable Class<T> clazz) {
+    @Nullable
+    final <T> T getValue(String key, @Nullable Class<T> clazz, @Nullable Class<?>... itemTypes) {
         int i = mMap.indexOfKey(key);
-        return (i >= 0) ? getValueAt(i, clazz) : null;
+        return (i >= 0) ? getValueAt(i, clazz, itemTypes) : null;
     }
 
     /**
-     * Returns the value for a certain position in the array map.
+     * Returns the value for a certain position in the array map for expected return type {@code
+     * clazz} (or pass {@code null} for no type check).
+     *
+     * For {@code itemTypes}, see {@link Parcel#readValue(int, ClassLoader, Class, Class[])}.
      *
      * This call should always be made after {@link #unparcel()} or inside a lock after making sure
      * {@code mMap} is not null.
@@ -349,11 +369,12 @@
      * @hide
      */
     @SuppressWarnings("unchecked")
-    final <T> T getValueAt(int i, @Nullable Class<T> clazz) {
+    @Nullable
+    final <T> T getValueAt(int i, @Nullable Class<T> clazz, @Nullable Class<?>... itemTypes) {
         Object object = mMap.valueAt(i);
-        if (object instanceof Function<?, ?>) {
+        if (object instanceof BiFunction<?, ?, ?>) {
             try {
-                object = ((Function<Class<?>, ?>) object).apply(clazz);
+                object = ((BiFunction<Class<?>, Class<?>[], ?>) object).apply(clazz, itemTypes);
             } catch (BadParcelableException e) {
                 if (sShouldDefuse) {
                     Log.w(TAG, "Failed to parse item " + mMap.keyAt(i) + ", returning null.", e);
@@ -615,7 +636,11 @@
      *
      * @param key a String key
      * @return an Object, or null
+     *
+     * @deprecated Use the type-safe specific APIs depending on the type of the item to be
+     *      retrieved, eg. {@link #getString(String)}.
      */
+    @Deprecated
     @Nullable
     public Object get(String key) {
         unparcel();
@@ -623,6 +648,32 @@
     }
 
     /**
+     * Returns the object of type {@code clazz} for the given {@code key}, or {@code null} if:
+     * <ul>
+     *     <li>No mapping of the desired type exists for the given key.
+     *     <li>A {@code null} value is explicitly associated with the key.
+     *     <li>The object is not of type {@code clazz}.
+     * </ul>
+     *
+     * <p>Use the more specific APIs where possible, especially in the case of containers such as
+     * lists, since those APIs allow you to specify the type of the items.
+     *
+     * @param key String key
+     * @param clazz The type of the object expected
+     * @return an Object, or null
+     */
+    @Nullable
+    <T> T get(@Nullable String key, @NonNull Class<T> clazz) {
+        unparcel();
+        try {
+            return getValue(key, requireNonNull(clazz));
+        } catch (ClassCastException | BadTypeParcelableException e) {
+            typeWarning(key, clazz.getCanonicalName(), e);
+            return null;
+        }
+    }
+
+    /**
      * Removes any entry with the given key from the mapping of this Bundle.
      *
      * @param key a String key
@@ -1006,7 +1057,7 @@
             sb.append(" but value was a ");
             sb.append(value.getClass().getName());
         } else {
-            sb.append(" but value was of a different type ");
+            sb.append(" but value was of a different type");
         }
         sb.append(".  The default value ");
         sb.append(defaultValue);
@@ -1019,6 +1070,10 @@
         typeWarning(key, value, className, "<null>", e);
     }
 
+    void typeWarning(String key, String className, RuntimeException e) {
+        typeWarning(key, /* value */ null, className, "<null>", e);
+    }
+
     /**
      * Returns the value associated with the given key, or defaultValue if
      * no mapping of the desired type exists for the given key.
@@ -1358,7 +1413,11 @@
      *
      * @param key a String, or null
      * @return a Serializable value, or null
+     *
+     * @deprecated Use {@link #getSerializable(String, Class)}. This method should only be used in
+     *      other deprecated APIs.
      */
+    @Deprecated
     @Nullable
     Serializable getSerializable(@Nullable String key) {
         unparcel();
@@ -1375,6 +1434,36 @@
     }
 
     /**
+     * Returns the value associated with the given key, or {@code null} if:
+     * <ul>
+     *     <li>No mapping of the desired type exists for the given key.
+     *     <li>A {@code null} value is explicitly associated with the key.
+     *     <li>The object is not of type {@code clazz}.
+     * </ul>
+     *
+     * @param key a String, or null
+     * @param clazz The expected class of the returned type
+     * @return a Serializable value, or null
+     */
+    @Nullable
+    <T extends Serializable> T getSerializable(@Nullable String key, @NonNull Class<T> clazz) {
+        return get(key, clazz);
+    }
+
+
+    @SuppressWarnings("unchecked")
+    @Nullable
+    <T> ArrayList<T> getArrayList(@Nullable String key, @NonNull Class<T> clazz) {
+        unparcel();
+        try {
+            return getValue(key, ArrayList.class, requireNonNull(clazz));
+        } catch (ClassCastException | BadTypeParcelableException e) {
+            typeWarning(key, "ArrayList<" + clazz.getCanonicalName() + ">", e);
+            return null;
+        }
+    }
+
+    /**
      * Returns the value associated with the given key, or null if
      * no mapping of the desired type exists for the given key or a null
      * value is explicitly associated with the key.
@@ -1384,17 +1473,7 @@
      */
     @Nullable
     ArrayList<Integer> getIntegerArrayList(@Nullable String key) {
-        unparcel();
-        Object o = getValue(key);
-        if (o == null) {
-            return null;
-        }
-        try {
-            return (ArrayList<Integer>) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "ArrayList<Integer>", e);
-            return null;
-        }
+        return getArrayList(key, Integer.class);
     }
 
     /**
@@ -1407,17 +1486,7 @@
      */
     @Nullable
     ArrayList<String> getStringArrayList(@Nullable String key) {
-        unparcel();
-        Object o = getValue(key);
-        if (o == null) {
-            return null;
-        }
-        try {
-            return (ArrayList<String>) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "ArrayList<String>", e);
-            return null;
-        }
+        return getArrayList(key, String.class);
     }
 
     /**
@@ -1430,17 +1499,7 @@
      */
     @Nullable
     ArrayList<CharSequence> getCharSequenceArrayList(@Nullable String key) {
-        unparcel();
-        Object o = getValue(key);
-        if (o == null) {
-            return null;
-        }
-        try {
-            return (ArrayList<CharSequence>) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "ArrayList<CharSequence>", e);
-            return null;
-        }
+        return getArrayList(key, CharSequence.class);
     }
 
     /**
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index de1dc80..cc58885 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -2700,6 +2700,46 @@
             @ServiceState.FrequencyRange int frequencyRange, int signalStrength,
             long elapsedRealtimeMs);
 
+    /**
+     * Returns the time in microseconds that the mobile radio has been actively transmitting data on
+     * a given Radio Access Technology (RAT), at a given frequency (NR RAT only), for a given
+     * transmission power level.
+     *
+     * @param rat            Radio Access Technology {@see RadioAccessTechnology}
+     * @param frequencyRange frequency range {@see ServiceState.FrequencyRange}, only needed for
+     *                       RADIO_ACCESS_TECHNOLOGY_NR. Use
+     *                       {@link ServiceState.FREQUENCY_RANGE_UNKNOWN} for other Radio Access
+     *                       Technologies.
+     * @param signalStrength the cellular signal strength. {@see CellSignalStrength#getLevel()}
+     * @param elapsedRealtimeMs current elapsed realtime
+     * @return time (in milliseconds) the mobile radio spent actively transmitting data in the
+     *         specified state, while on battery. Returns {@link DURATION_UNAVAILABLE} if
+     *         data unavailable.
+     * @hide
+     */
+    public abstract long getActiveTxRadioDurationMs(@RadioAccessTechnology int rat,
+            @ServiceState.FrequencyRange int frequencyRange, int signalStrength,
+            long elapsedRealtimeMs);
+
+    /**
+     * Returns the time in microseconds that the mobile radio has been actively receiving data on a
+     * given Radio Access Technology (RAT), at a given frequency (NR RAT only), for a given
+     * transmission power level.
+     *
+     * @param rat            Radio Access Technology {@see RadioAccessTechnology}
+     * @param frequencyRange frequency range {@see ServiceState.FrequencyRange}, only needed for
+     *                       RADIO_ACCESS_TECHNOLOGY_NR. Use
+     *                       {@link ServiceState.FREQUENCY_RANGE_UNKNOWN} for other Radio Access
+     *                       Technologies.
+     * @param elapsedRealtimeMs current elapsed realtime
+     * @return time (in milliseconds) the mobile radio spent actively receiving data in the
+     *         specified state, while on battery. Returns {@link DURATION_UNAVAILABLE} if
+     *         data unavailable.
+     * @hide
+     */
+    public abstract long getActiveRxRadioDurationMs(@RadioAccessTechnology int rat,
+            @ServiceState.FrequencyRange int frequencyRange, long elapsedRealtimeMs);
+
     static final String[] WIFI_SUPPL_STATE_NAMES = {
         "invalid", "disconn", "disabled", "inactive", "scanning",
         "authenticating", "associating", "associated", "4-way-handshake",
@@ -2720,6 +2760,13 @@
     public static final long POWER_DATA_UNAVAILABLE = -1L;
 
     /**
+     * Returned value if duration data is unavailable.
+     *
+     * {@hide}
+     */
+    public static final long DURATION_UNAVAILABLE = -1L;
+
+    /**
      * Returns the battery consumption (in microcoulombs) of bluetooth, derived from on
      * device power measurement data.
      * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
@@ -4093,6 +4140,10 @@
                 "    Mmwave frequency (greater than 6GHz):\n"};
         final String signalStrengthHeader =
                 "      Signal Strength Time:\n";
+        final String txHeader =
+                "      Tx Time:\n";
+        final String rxHeader =
+                "      Rx Time: ";
         final String[] signalStrengthDescription = new String[]{
                 "        unknown:  ",
                 "        poor:     ",
@@ -4144,6 +4195,29 @@
                     sb.append(")\n");
                 }
 
+                sb.append(prefix);
+                sb.append(txHeader);
+                for (int strength = 0; strength < numSignalStrength; strength++) {
+                    final long timeMs = getActiveTxRadioDurationMs(rat, freqLvl, strength,
+                            rawRealtimeMs);
+                    if (timeMs <= 0) continue;
+                    hasFreqData = true;
+                    sb.append(prefix);
+                    sb.append(signalStrengthDescription[strength]);
+                    formatTimeMs(sb, timeMs);
+                    sb.append("(");
+                    sb.append(formatRatioLocked(timeMs, totalActiveTimesMs));
+                    sb.append(")\n");
+                }
+
+                sb.append(prefix);
+                sb.append(rxHeader);
+                final long rxTimeMs = getActiveRxRadioDurationMs(rat, freqLvl, rawRealtimeMs);
+                formatTimeMs(sb, rxTimeMs);
+                sb.append("(");
+                sb.append(formatRatioLocked(rxTimeMs, totalActiveTimesMs));
+                sb.append(")\n");
+
                 if (hasFreqData) {
                     hasData = true;
                     pw.print(sb);
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index 2b13f20..edbbb59 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.util.ArrayMap;
 import android.util.Size;
@@ -876,7 +877,7 @@
     @Nullable
     public Bundle getBundle(@Nullable String key) {
         unparcel();
-        Object o = getValue(key);
+        Object o = mMap.get(key);
         if (o == null) {
             return null;
         }
@@ -899,7 +900,11 @@
      *
      * @param key a String, or {@code null}
      * @return a Parcelable value, or {@code null}
+     *
+     * @deprecated Use the type-safer {@link #getParcelable(String, Class)} starting from Android
+     *      {@link Build.VERSION_CODES#TIRAMISU}.
      */
+    @Deprecated
     @Nullable
     public <T extends Parcelable> T getParcelable(@Nullable String key) {
         unparcel();
@@ -916,30 +921,28 @@
     }
 
     /**
-     * Returns the value associated with the given key, or {@code null} if
-     * no mapping of the desired type exists for the given key or a {@code null}
-     * value is explicitly associated with the key.
+     * Returns the value associated with the given key or {@code null} if:
+     * <ul>
+     *     <li>No mapping of the desired type exists for the given key.
+     *     <li>A {@code null} value is explicitly associated with the key.
+     *     <li>The object is not of type {@code clazz}.
+     * </ul>
      *
      * <p><b>Note: </b> if the expected value is not a class provided by the Android platform,
      * you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first.
      * Otherwise, this method might throw an exception or return {@code null}.
      *
      * @param key a String, or {@code null}
-     * @param clazz The type of the object expected or {@code null} for performing no checks.
+     * @param clazz The type of the object expected
      * @return a Parcelable value, or {@code null}
-     *
-     * @hide
      */
     @SuppressWarnings("unchecked")
     @Nullable
     public <T> T getParcelable(@Nullable String key, @NonNull Class<T> clazz) {
-        unparcel();
-        try {
-            return getValue(key, requireNonNull(clazz));
-        } catch (ClassCastException | BadParcelableException e) {
-            typeWarning(key, /* value */ null, "Parcelable", e);
-            return null;
-        }
+        // The reason for not using <T extends Parcelable> is because the caller could provide a
+        // super class to restrict the children that doesn't implement Parcelable itself while the
+        // children do, more details at b/210800751 (same reasoning applies here).
+        return get(key, clazz);
     }
 
     /**
@@ -953,7 +956,11 @@
      *
      * @param key a String, or {@code null}
      * @return a Parcelable[] value, or {@code null}
+     *
+     * @deprecated Use the type-safer {@link #getParcelableArray(String, Class)} starting from
+     *      Android {@link Build.VERSION_CODES#TIRAMISU}.
      */
+    @Deprecated
     @Nullable
     public Parcelable[] getParcelableArray(@Nullable String key) {
         unparcel();
@@ -970,6 +977,39 @@
     }
 
     /**
+     * Returns the value associated with the given key, or {@code null} if:
+     * <ul>
+     *     <li>No mapping of the desired type exists for the given key.
+     *     <li>A {@code null} value is explicitly associated with the key.
+     *     <li>The object is not of type {@code clazz}.
+     * </ul>
+     *
+     * <p><b>Note: </b> if the expected value is not a class provided by the Android platform,
+     * you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first.
+     * Otherwise, this method might throw an exception or return {@code null}.
+     *
+     * @param key a String, or {@code null}
+     * @param clazz The type of the items inside the array
+     * @return a Parcelable[] value, or {@code null}
+     */
+    @SuppressLint({"ArrayReturn", "NullableCollection"})
+    @SuppressWarnings("unchecked")
+    @Nullable
+    public <T> T[] getParcelableArray(@Nullable String key, @NonNull Class<T> clazz) {
+        // The reason for not using <T extends Parcelable> is because the caller could provide a
+        // super class to restrict the children that doesn't implement Parcelable itself while the
+        // children do, more details at b/210800751 (same reasoning applies here).
+        unparcel();
+        try {
+            // In Java 12, we can pass clazz.arrayType() instead of Parcelable[] and later casting.
+            return (T[]) getValue(key, Parcelable[].class, requireNonNull(clazz));
+        } catch (ClassCastException | BadTypeParcelableException e) {
+            typeWarning(key, clazz.getCanonicalName() + "[]", e);
+            return null;
+        }
+    }
+
+    /**
      * Returns the value associated with the given key, or {@code null} if
      * no mapping of the desired type exists for the given key or a {@code null}
      * value is explicitly associated with the key.
@@ -980,7 +1020,11 @@
      *
      * @param key a String, or {@code null}
      * @return an ArrayList<T> value, or {@code null}
+     *
+     * @deprecated Use the type-safer {@link #getParcelable(String, Class)} starting from Android
+     *      {@link Build.VERSION_CODES#TIRAMISU}.
      */
+    @Deprecated
     @Nullable
     public <T extends Parcelable> ArrayList<T> getParcelableArrayList(@Nullable String key) {
         unparcel();
@@ -997,14 +1041,43 @@
     }
 
     /**
+     * Returns the value associated with the given key, or {@code null} if:
+     * <ul>
+     *     <li>No mapping of the desired type exists for the given key.
+     *     <li>A {@code null} value is explicitly associated with the key.
+     *     <li>The object is not of type {@code clazz}.
+     * </ul>
+     *
+     * <p><b>Note: </b> if the expected value is not a class provided by the Android platform,
+     * you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first.
+     * Otherwise, this method might throw an exception or return {@code null}.
+     *
+     * @param key   a String, or {@code null}
+     * @param clazz The type of the items inside the array list
+     * @return an ArrayList<T> value, or {@code null}
+     */
+    @SuppressLint("NullableCollection")
+    @SuppressWarnings("unchecked")
+    @Nullable
+    public <T> ArrayList<T> getParcelableArrayList(@Nullable String key, @NonNull Class<T> clazz) {
+        // The reason for not using <T extends Parcelable> is because the caller could provide a
+        // super class to restrict the children that doesn't implement Parcelable itself while the
+        // children do, more details at b/210800751 (same reasoning applies here).
+        return getArrayList(key, clazz);
+    }
+
+    /**
      * Returns the value associated with the given key, or null if
      * no mapping of the desired type exists for the given key or a null
      * value is explicitly associated with the key.
      *
      * @param key a String, or null
-     *
      * @return a SparseArray of T values, or null
+     *
+     * @deprecated Use the type-safer {@link #getSparseParcelableArray(String, Class)} starting from
+     *      Android {@link Build.VERSION_CODES#TIRAMISU}.
      */
+    @Deprecated
     @Nullable
     public <T extends Parcelable> SparseArray<T> getSparseParcelableArray(@Nullable String key) {
         unparcel();
@@ -1021,13 +1094,44 @@
     }
 
     /**
+     * Returns the value associated with the given key, or {@code null} if:
+     * <ul>
+     *     <li>No mapping of the desired type exists for the given key.
+     *     <li>A {@code null} value is explicitly associated with the key.
+     *     <li>The object is not of type {@code clazz}.
+     * </ul>
+     *
+     * @param key a String, or null
+     * @return a SparseArray of T values, or null
+     */
+    @SuppressWarnings("unchecked")
+    @Nullable
+    public <T> SparseArray<T> getSparseParcelableArray(@Nullable String key,
+            @NonNull Class<T> clazz) {
+        // The reason for not using <T extends Parcelable> is because the caller could provide a
+        // super class to restrict the children that doesn't implement Parcelable itself while the
+        // children do, more details at b/210800751 (same reasoning applies here).
+        unparcel();
+        try {
+            return (SparseArray<T>) getValue(key, SparseArray.class, requireNonNull(clazz));
+        } catch (ClassCastException | BadTypeParcelableException e) {
+            typeWarning(key, "SparseArray<" + clazz.getCanonicalName() + ">", e);
+            return null;
+        }
+    }
+
+    /**
      * Returns the value associated with the given key, or null if
      * no mapping of the desired type exists for the given key or a null
      * value is explicitly associated with the key.
      *
      * @param key a String, or null
      * @return a Serializable value, or null
+     *
+     * @deprecated Use the type-safer {@link #getSerializable(String, Class)} starting from Android
+     *      {@link Build.VERSION_CODES#TIRAMISU}.
      */
+    @Deprecated
     @Override
     @Nullable
     public Serializable getSerializable(@Nullable String key) {
@@ -1035,6 +1139,24 @@
     }
 
     /**
+     * Returns the value associated with the given key, or {@code null} if:
+     * <ul>
+     *     <li>No mapping of the desired type exists for the given key.
+     *     <li>A {@code null} value is explicitly associated with the key.
+     *     <li>The object is not of type {@code clazz}.
+     * </ul>
+     *
+     * @param key   a String, or null
+     * @param clazz The expected class of the returned type
+     * @return a Serializable value, or null
+     */
+    @Nullable
+    public <T extends Serializable> T getSerializable(@Nullable String key,
+            @NonNull Class<T> clazz) {
+        return super.getSerializable(key, requireNonNull(clazz));
+    }
+
+    /**
      * Returns the value associated with the given key, or null if
      * no mapping of the desired type exists for the given key or a null
      * value is explicitly associated with the key.
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index ae92353..09cfb6e 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -16,6 +16,8 @@
 
 package android.os;
 
+import static com.android.internal.util.Preconditions.checkArgument;
+
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.IntDef;
@@ -65,6 +67,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.function.BiFunction;
 import java.util.function.Function;
 import java.util.function.IntFunction;
 import java.util.function.Supplier;
@@ -287,26 +290,26 @@
     private static final int VAL_NULL = -1;
     private static final int VAL_STRING = 0;
     private static final int VAL_INTEGER = 1;
-    private static final int VAL_MAP = 2;
+    private static final int VAL_MAP = 2; // length-prefixed
     private static final int VAL_BUNDLE = 3;
-    private static final int VAL_PARCELABLE = 4;
+    private static final int VAL_PARCELABLE = 4; // length-prefixed
     private static final int VAL_SHORT = 5;
     private static final int VAL_LONG = 6;
     private static final int VAL_FLOAT = 7;
     private static final int VAL_DOUBLE = 8;
     private static final int VAL_BOOLEAN = 9;
     private static final int VAL_CHARSEQUENCE = 10;
-    private static final int VAL_LIST  = 11;
-    private static final int VAL_SPARSEARRAY = 12;
+    private static final int VAL_LIST  = 11; // length-prefixed
+    private static final int VAL_SPARSEARRAY = 12; // length-prefixed
     private static final int VAL_BYTEARRAY = 13;
     private static final int VAL_STRINGARRAY = 14;
     private static final int VAL_IBINDER = 15;
-    private static final int VAL_PARCELABLEARRAY = 16;
-    private static final int VAL_OBJECTARRAY = 17;
+    private static final int VAL_PARCELABLEARRAY = 16; // length-prefixed
+    private static final int VAL_OBJECTARRAY = 17; // length-prefixed
     private static final int VAL_INTARRAY = 18;
     private static final int VAL_LONGARRAY = 19;
     private static final int VAL_BYTE = 20;
-    private static final int VAL_SERIALIZABLE = 21;
+    private static final int VAL_SERIALIZABLE = 21; // length-prefixed
     private static final int VAL_SPARSEBOOLEANARRAY = 22;
     private static final int VAL_BOOLEANARRAY = 23;
     private static final int VAL_CHARSEQUENCEARRAY = 24;
@@ -3179,8 +3182,7 @@
      */
     @Deprecated
     public final void readMap(@NonNull Map outVal, @Nullable ClassLoader loader) {
-        int n = readInt();
-        readMapInternal(outVal, n, loader, /* clazzKey */ null, /* clazzValue */ null);
+        readMapInternal(outVal, loader, /* clazzKey */ null, /* clazzValue */ null);
     }
 
     /**
@@ -3195,8 +3197,7 @@
             @NonNull Class<V> clazzValue) {
         Objects.requireNonNull(clazzKey);
         Objects.requireNonNull(clazzValue);
-        int n = readInt();
-        readMapInternal(outVal, n, loader, clazzKey, clazzValue);
+        readMapInternal(outVal, loader, clazzKey, clazzValue);
     }
 
     /**
@@ -3245,13 +3246,7 @@
     @Deprecated
     @Nullable
     public HashMap readHashMap(@Nullable ClassLoader loader) {
-        int n = readInt();
-        if (n < 0) {
-            return null;
-        }
-        HashMap m = new HashMap(n);
-        readMapInternal(m, n, loader, /* clazzKey */ null, /* clazzValue */ null);
-        return m;
+        return readHashMapInternal(loader, /* clazzKey */ null, /* clazzValue */ null);
     }
 
     /**
@@ -3267,13 +3262,7 @@
             @NonNull Class<? extends K> clazzKey, @NonNull Class<? extends V> clazzValue) {
         Objects.requireNonNull(clazzKey);
         Objects.requireNonNull(clazzValue);
-        int n = readInt();
-        if (n < 0) {
-            return null;
-        }
-        HashMap<K, V> map = new HashMap<>(n);
-        readMapInternal(map, n, loader, clazzKey, clazzValue);
-        return map;
+        return readHashMapInternal(loader, clazzKey, clazzValue);
     }
 
     /**
@@ -4296,16 +4285,17 @@
 
 
     /**
-     * @param clazz The type of the object expected or {@code null} for performing no checks.
+     * @see #readValue(int, ClassLoader, Class, Class[])
      */
     @Nullable
-    private <T> T readValue(@Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+    private <T> T readValue(@Nullable ClassLoader loader, @Nullable Class<T> clazz,
+            @Nullable Class<?>... itemTypes) {
         int type = readInt();
         final T object;
         if (isLengthPrefixed(type)) {
             int length = readInt();
             int start = dataPosition();
-            object = readValue(type, loader, clazz);
+            object = readValue(type, loader, clazz, itemTypes);
             int actual = dataPosition() - start;
             if (actual != length) {
                 Slog.wtfStack(TAG,
@@ -4313,25 +4303,26 @@
                                 + "  consumed " + actual + " bytes, but " + length + " expected.");
             }
         } else {
-            object = readValue(type, loader, clazz);
+            object = readValue(type, loader, clazz, itemTypes);
         }
         return object;
     }
 
     /**
-     * This will return a {@link Function} for length-prefixed types that deserializes the object
-     * when {@link Function#apply} is called with the expected class of the return object (or {@code
-     * null} for no type check), for other types it will return the object itself.
+     * This will return a {@link BiFunction} for length-prefixed types that deserializes the object
+     * when {@link BiFunction#apply} is called (the arguments correspond to the ones of {@link
+     * #readValue(int, ClassLoader, Class, Class[])} after the class loader), for other types it
+     * will return the object itself.
      *
-     * <p>After calling {@link Function#apply(Object)} the parcel cursor will not change. Note that
-     * you shouldn't recycle the parcel, not at least until all objects have been retrieved. No
+     * <p>After calling {@link BiFunction#apply} the parcel cursor will not change. Note that you
+     * shouldn't recycle the parcel, not at least until all objects have been retrieved. No
      * synchronization attempts are made.
      *
      * </p>The function returned implements {@link #equals(Object)} and {@link #hashCode()}. Two
      * function objects are equal if either of the following is true:
      * <ul>
-     *   <li>{@link Function#apply} has been called on both and both objects returned are equal.
-     *   <li>{@link Function#apply} hasn't been called on either one and everything below is true:
+     *   <li>{@link BiFunction#apply} has been called on both and both objects returned are equal.
+     *   <li>{@link BiFunction#apply} hasn't been called on either one and everything below is true:
      *   <ul>
      *       <li>The {@code loader} parameters used to retrieve each are equal.
      *       <li>They both have the same type.
@@ -4358,7 +4349,7 @@
     }
 
 
-    private static final class LazyValue implements Function<Class<?>, Object> {
+    private static final class LazyValue implements BiFunction<Class<?>, Class<?>[], Object> {
         /**
          *                      |   4B   |   4B   |
          * mSource = Parcel{... |  type  | length | object | ...}
@@ -4390,7 +4381,7 @@
         }
 
         @Override
-        public Object apply(@Nullable Class<?> clazz) {
+        public Object apply(@Nullable Class<?> clazz, @Nullable Class<?>[] itemTypes) {
             Parcel source = mSource;
             if (source != null) {
                 synchronized (source) {
@@ -4399,7 +4390,7 @@
                         int restore = source.dataPosition();
                         try {
                             source.setDataPosition(mPosition);
-                            mObject = source.readValue(mLoader, clazz);
+                            mObject = source.readValue(mLoader, clazz, itemTypes);
                         } finally {
                             source.setDataPosition(restore);
                         }
@@ -4479,14 +4470,25 @@
         }
     }
 
+    /** Same as {@link #readValue(ClassLoader, Class, Class[])} without any item types. */
+    private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+        // Avoids allocating Class[0] array
+        return readValue(type, loader, clazz, (Class<?>[]) null);
+    }
+
     /**
      * Reads a value from the parcel of type {@code type}. Does NOT read the int representing the
      * type first.
+     *
      * @param clazz The type of the object expected or {@code null} for performing no checks.
+     * @param itemTypes If the value is a container, these represent the item types (eg. for a list
+     *                  it's the item type, for a map, it's the key type, followed by the value
+     *                  type).
      */
     @SuppressWarnings("unchecked")
     @Nullable
-    private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+    private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz,
+            @Nullable Class<?>... itemTypes) {
         final Object object;
         switch (type) {
             case VAL_NULL:
@@ -4502,7 +4504,11 @@
                 break;
 
             case VAL_MAP:
-                object = readHashMap(loader);
+                checkTypeToUnparcel(clazz, HashMap.class);
+                Class<?> keyType = ArrayUtils.getOrNull(itemTypes, 0);
+                Class<?> valueType = ArrayUtils.getOrNull(itemTypes, 1);
+                checkArgument((keyType == null) == (valueType == null));
+                object = readHashMapInternal(loader, keyType, valueType);
                 break;
 
             case VAL_PARCELABLE:
@@ -4533,10 +4539,12 @@
                 object = readCharSequence();
                 break;
 
-            case VAL_LIST:
-                object = readArrayList(loader);
+            case VAL_LIST: {
+                checkTypeToUnparcel(clazz, ArrayList.class);
+                Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0);
+                object = readArrayListInternal(loader, itemType);
                 break;
-
+            }
             case VAL_BOOLEANARRAY:
                 object = createBooleanArray();
                 break;
@@ -4557,10 +4565,12 @@
                 object = readStrongBinder();
                 break;
 
-            case VAL_OBJECTARRAY:
-                object = readArray(loader);
+            case VAL_OBJECTARRAY: {
+                Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0);
+                checkArrayTypeToUnparcel(clazz, (itemType != null) ? itemType : Object.class);
+                object = readArrayInternal(loader, itemType);
                 break;
-
+            }
             case VAL_INTARRAY:
                 object = createIntArray();
                 break;
@@ -4577,14 +4587,18 @@
                 object = readSerializableInternal(loader, clazz);
                 break;
 
-            case VAL_PARCELABLEARRAY:
-                object = readParcelableArray(loader);
+            case VAL_PARCELABLEARRAY: {
+                Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0);
+                checkArrayTypeToUnparcel(clazz, (itemType != null) ? itemType : Parcelable.class);
+                object = readParcelableArrayInternal(loader, itemType);
                 break;
-
-            case VAL_SPARSEARRAY:
-                object = readSparseArray(loader);
+            }
+            case VAL_SPARSEARRAY: {
+                checkTypeToUnparcel(clazz, SparseArray.class);
+                Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0);
+                object = readSparseArrayInternal(loader, itemType);
                 break;
-
+            }
             case VAL_SPARSEBOOLEANARRAY:
                 object = readSparseBooleanArray();
                 break;
@@ -4632,7 +4646,7 @@
                             + " at offset " + off);
         }
         if (object != null && clazz != null && !clazz.isInstance(object)) {
-            throw new BadParcelableException("Unparcelled object " + object
+            throw new BadTypeParcelableException("Unparcelled object " + object
                     + " is not an instance of required class " + clazz.getName()
                     + " provided in the parameter");
         }
@@ -4659,6 +4673,38 @@
     }
 
     /**
+     * Checks that an array of type T[], where T is {@code componentTypeToUnparcel}, is a subtype of
+     * {@code requiredArrayType}.
+     */
+    private void checkArrayTypeToUnparcel(@Nullable Class<?> requiredArrayType,
+            Class<?> componentTypeToUnparcel) {
+        if (requiredArrayType != null) {
+            // In Java 12, we could use componentTypeToUnparcel.arrayType() for the check
+            Class<?> requiredComponentType = requiredArrayType.getComponentType();
+            if (requiredComponentType == null) {
+                throw new BadTypeParcelableException(
+                        "About to unparcel an array but type "
+                                + requiredArrayType.getCanonicalName()
+                                + " required by caller is not an array.");
+            }
+            checkTypeToUnparcel(requiredComponentType, componentTypeToUnparcel);
+        }
+    }
+
+    /**
+     * Checks that {@code typeToUnparcel} is a subtype of {@code requiredType}, if {@code
+     * requiredType} is not {@code null}.
+     */
+    private void checkTypeToUnparcel(@Nullable Class<?> requiredType, Class<?> typeToUnparcel) {
+        if (requiredType != null && !requiredType.isAssignableFrom(typeToUnparcel)) {
+            throw new BadTypeParcelableException(
+                    "About to unparcel a " + typeToUnparcel.getCanonicalName()
+                            + ", which is not a subtype of type " + requiredType.getCanonicalName()
+                            + " required by caller.");
+        }
+    }
+
+    /**
      * Read and return a new Parcelable from the parcel.  The given class loader
      * will be used to load any enclosed Parcelables.  If it is null, the default
      * class loader will be used.
@@ -4788,7 +4834,7 @@
             if (clazz != null) {
                 Class<?> parcelableClass = creator.getClass().getEnclosingClass();
                 if (!clazz.isAssignableFrom(parcelableClass)) {
-                    throw new BadParcelableException("Parcelable creator " + name + " is not "
+                    throw new BadTypeParcelableException("Parcelable creator " + name + " is not "
                             + "a subclass of required class " + clazz.getName()
                             + " provided in the parameter");
                 }
@@ -4811,7 +4857,7 @@
             }
             if (clazz != null) {
                 if (!clazz.isAssignableFrom(parcelableClass)) {
-                    throw new BadParcelableException("Parcelable creator " + name + " is not "
+                    throw new BadTypeParcelableException("Parcelable creator " + name + " is not "
                             + "a subclass of required class " + clazz.getName()
                             + " provided in the parameter");
                 }
@@ -4872,15 +4918,7 @@
     @Deprecated
     @Nullable
     public Parcelable[] readParcelableArray(@Nullable ClassLoader loader) {
-        int N = readInt();
-        if (N < 0) {
-            return null;
-        }
-        Parcelable[] p = new Parcelable[N];
-        for (int i = 0; i < N; i++) {
-            p[i] = readParcelable(loader);
-        }
-        return p;
+        return readParcelableArrayInternal(loader, /* clazz */ null);
     }
 
     /**
@@ -4892,14 +4930,20 @@
      * trying to instantiate an element.
      */
     @SuppressLint({"ArrayReturn", "NullableCollection"})
-    @SuppressWarnings("unchecked")
     @Nullable
     public <T> T[] readParcelableArray(@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+        return readParcelableArrayInternal(loader, requireNonNull(clazz));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Nullable
+    private <T> T[] readParcelableArrayInternal(@Nullable ClassLoader loader,
+            @Nullable Class<T> clazz) {
         int n = readInt();
         if (n < 0) {
             return null;
         }
-        T[] p = (T[]) Array.newInstance(clazz, n);
+        T[] p = (T[]) ((clazz == null) ? new Parcelable[n] : Array.newInstance(clazz, n));
         for (int i = 0; i < n; i++) {
             p[i] = readParcelableInternal(loader, clazz);
         }
@@ -4962,7 +5006,7 @@
                 // the class the same way as ObjectInputStream, using the provided classloader.
                 Class<?> cl = Class.forName(name, false, loader);
                 if (!clazz.isAssignableFrom(cl)) {
-                    throw new BadParcelableException("Serializable object "
+                    throw new BadTypeParcelableException("Serializable object "
                             + cl.getName() + " is not a subclass of required class "
                             + clazz.getName() + " provided in the parameter");
                 }
@@ -4987,7 +5031,7 @@
                 // the deserialized object, as we cannot resolve the class the same way as
                 // ObjectInputStream.
                 if (!clazz.isAssignableFrom(object.getClass())) {
-                    throw new BadParcelableException("Serializable object "
+                    throw new BadTypeParcelableException("Serializable object "
                             + object.getClass().getName() + " is not a subclass of required class "
                             + clazz.getName() + " provided in the parameter");
                 }
@@ -5097,7 +5141,26 @@
         readMapInternal(outVal, n, loader, /* clazzKey */null, /* clazzValue */null);
     }
 
-    /* package */ <K, V> void readMapInternal(@NonNull Map<? super K, ? super V> outVal, int n,
+    @Nullable
+    private <K, V> HashMap<K, V> readHashMapInternal(@Nullable ClassLoader loader,
+            @NonNull Class<? extends K> clazzKey, @NonNull Class<? extends V> clazzValue) {
+        int n = readInt();
+        if (n < 0) {
+            return null;
+        }
+        HashMap<K, V> map = new HashMap<>(n);
+        readMapInternal(map, n, loader, clazzKey, clazzValue);
+        return map;
+    }
+
+    private <K, V> void readMapInternal(@NonNull Map<? super K, ? super V> outVal,
+            @Nullable ClassLoader loader, @Nullable Class<K> clazzKey,
+            @Nullable Class<V> clazzValue) {
+        int n = readInt();
+        readMapInternal(outVal, n, loader, clazzKey, clazzValue);
+    }
+
+    private <K, V> void readMapInternal(@NonNull Map<? super K, ? super V> outVal, int n,
             @Nullable ClassLoader loader, @Nullable Class<K> clazzKey,
             @Nullable Class<V> clazzValue) {
         while (n > 0) {
@@ -5108,7 +5171,7 @@
         }
     }
 
-    /* package */ void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal,
+    private void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal,
             int size, @Nullable ClassLoader loader) {
         readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loader);
     }
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 315eef7..e3be4d3 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -183,25 +183,29 @@
     /**
      * Wake lock flag: Turn the screen on when the wake lock is acquired.
      * <p>
-     * Normally wake locks don't actually wake the device, they just cause
-     * the screen to remain on once it's already on.  Think of the video player
-     * application as the normal behavior.  Notifications that pop up and want
-     * the device to be on are the exception; use this flag to be like them.
+     * Normally wake locks don't actually wake the device, they just cause the screen to remain on
+     * once it's already on. This flag will cause the device to wake up when the wake lock is
+     * acquired.
      * </p><p>
      * Android TV playback devices attempt to turn on the HDMI-connected TV via HDMI-CEC on any
      * wake-up, including wake-ups triggered by wake locks.
      * </p><p>
      * Cannot be used with {@link #PARTIAL_WAKE_LOCK}.
      * </p>
+     *
+     * @deprecated Most applications should use {@link android.R.attr#turnScreenOn} or
+     * {@link android.app.Activity#setTurnScreenOn(boolean)} instead, as this prevents the previous
+     * foreground app from being resumed first when the screen turns on. Note that this flag may
+     * require a permission in the future.
      */
+    @Deprecated
     public static final int ACQUIRE_CAUSES_WAKEUP = 0x10000000;
 
     /**
      * Wake lock flag: When this wake lock is released, poke the user activity timer
      * so the screen stays on for a little longer.
      * <p>
-     * Will not turn the screen on if it is not already on.
-     * See {@link #ACQUIRE_CAUSES_WAKEUP} if you want that.
+     * This will not turn the screen on if it is not already on.
      * </p><p>
      * Cannot be used with {@link #PARTIAL_WAKE_LOCK}.
      * </p>
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index cc98339..48e2827 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -3022,9 +3022,17 @@
         }
     }
 
-    /** @hide */
-    @TestApi
+    /**
+     * Returns the authority of the current cloud media provider that was set by the
+     * {@link android.service.storage.ExternalStorageService} holding the
+     * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE} permission via
+     * {@link #setCloudMediaProvider(String)}.
+     *
+     * @hide
+     */
     @Nullable
+    @TestApi
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public String getCloudMediaProvider() {
         try {
             return mStorageManager.getCloudMediaProvider();
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 23e02e9..11b5d44 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -24,7 +24,6 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
-import android.annotation.StringDef;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
@@ -2128,7 +2127,7 @@
     /**
      * Intent extra: The id of a setting restricted by supervisors.
      * <p>
-     * Type: String with a value from the SupervisorVerificationSetting annotation below.
+     * Type: Integer with a value from the SupervisorVerificationSetting annotation below.
      * <ul>
      * <li>{@link #SUPERVISOR_VERIFICATION_SETTING_UNKNOWN}
      * <li>{@link #SUPERVISOR_VERIFICATION_SETTING_BIOMETRICS}
@@ -2141,20 +2140,19 @@
     /**
      * Unknown setting.
      */
-    public static final String SUPERVISOR_VERIFICATION_SETTING_UNKNOWN = "";
+    public static final int SUPERVISOR_VERIFICATION_SETTING_UNKNOWN = 0;
 
     /**
      * Biometric settings for supervisors.
      */
-    public static final String SUPERVISOR_VERIFICATION_SETTING_BIOMETRICS =
-            "supervisor_restricted_biometrics_controller";
+    public static final int SUPERVISOR_VERIFICATION_SETTING_BIOMETRICS = 1;
 
     /**
      * Keys for {@link #EXTRA_SUPERVISOR_RESTRICTED_SETTING_KEY}.
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
-    @StringDef(prefix = { "SUPERVISOR_VERIFICATION_SETTING_" }, value = {
+    @IntDef(prefix = { "SUPERVISOR_VERIFICATION_SETTING_" }, value = {
             SUPERVISOR_VERIFICATION_SETTING_UNKNOWN,
             SUPERVISOR_VERIFICATION_SETTING_BIOMETRICS,
     })
@@ -9950,6 +9948,14 @@
         public static final String LOCKSCREEN_SHOW_CONTROLS = "lockscreen_show_controls";
 
         /**
+         * Whether trivial home controls can be used without authentication
+         *
+         * @hide
+         */
+        public static final String LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS =
+                "lockscreen_allow_trivial_controls";
+
+        /**
          * Whether wallet should be accessible from the lockscreen
          *
          * @hide
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index 163d6ed..bfc3b8b 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -36,6 +36,9 @@
     private static final String TAG = "DreamOverlayService";
     private static final boolean DEBUG = false;
     private boolean mShowComplications;
+    private boolean mIsPreviewMode;
+    @Nullable
+    private CharSequence mDreamLabel;
 
     private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
         @Override
@@ -56,6 +59,8 @@
     public final IBinder onBind(@NonNull Intent intent) {
         mShowComplications = intent.getBooleanExtra(DreamService.EXTRA_SHOW_COMPLICATIONS,
                 DreamService.DEFAULT_SHOW_COMPLICATIONS);
+        mIsPreviewMode = intent.getBooleanExtra(DreamService.EXTRA_IS_PREVIEW, false);
+        mDreamLabel = intent.getCharSequenceExtra(DreamService.EXTRA_DREAM_LABEL);
         return mDreamOverlay.asBinder();
     }
 
@@ -84,4 +89,19 @@
     public final boolean shouldShowComplications() {
         return mShowComplications;
     }
+
+    /**
+     * Returns whether the dream is running in preview mode.
+     */
+    public final boolean isPreviewMode() {
+        return mIsPreviewMode;
+    }
+
+    /**
+     * Returns the user-facing label of the currently running dream.
+     */
+    @Nullable
+    public final CharSequence getDreamLabel() {
+        return mDreamLabel;
+    }
 }
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 3459172..db622d3 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -216,6 +216,18 @@
             "android.service.dreams.SHOW_COMPLICATIONS";
 
     /**
+     * Extra containing a boolean for whether we are showing this dream in preview mode.
+     * @hide
+     */
+    public static final String EXTRA_IS_PREVIEW = "android.service.dreams.IS_PREVIEW";
+
+    /**
+     * The user-facing label of the current dream service.
+     * @hide
+     */
+    public static final String EXTRA_DREAM_LABEL = "android.service.dreams.DREAM_LABEL";
+
+    /**
      * The default value for whether to show complications on the overlay.
      * @hide
      */
@@ -258,15 +270,19 @@
         }
 
         public void bind(Context context, @Nullable ComponentName overlayService,
-                ComponentName dreamService) {
+                ComponentName dreamService, boolean isPreviewMode) {
             if (overlayService == null) {
                 return;
             }
 
+            final ServiceInfo serviceInfo = fetchServiceInfo(context, dreamService);
+
             final Intent overlayIntent = new Intent();
             overlayIntent.setComponent(overlayService);
             overlayIntent.putExtra(EXTRA_SHOW_COMPLICATIONS,
-                    fetchShouldShowComplications(context, dreamService));
+                    fetchShouldShowComplications(context, serviceInfo));
+            overlayIntent.putExtra(EXTRA_DREAM_LABEL, fetchDreamLabel(context, serviceInfo));
+            overlayIntent.putExtra(EXTRA_IS_PREVIEW, isPreviewMode);
 
             context.bindService(overlayIntent,
                     this, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE);
@@ -988,8 +1004,11 @@
 
         // Connect to the overlay service if present.
         if (!mWindowless) {
-            mOverlayConnection.bind(this, intent.getParcelableExtra(EXTRA_DREAM_OVERLAY_COMPONENT),
-                    new ComponentName(this, getClass()));
+            mOverlayConnection.bind(
+                    /* context= */ this,
+                    intent.getParcelableExtra(EXTRA_DREAM_OVERLAY_COMPONENT),
+                    new ComponentName(this, getClass()),
+                    intent.getBooleanExtra(EXTRA_IS_PREVIEW, /* defaultValue= */ false));
         }
 
         return mDreamServiceWrapper;
@@ -1111,7 +1130,10 @@
      * @hide
      */
     @Nullable
-    public static DreamMetadata getDreamMetadata(Context context, ServiceInfo serviceInfo) {
+    public static DreamMetadata getDreamMetadata(Context context,
+            @Nullable ServiceInfo serviceInfo) {
+        if (serviceInfo == null) return null;
+
         final PackageManager pm = context.getPackageManager();
 
         final TypedArray rawMetadata = readMetadata(pm, serviceInfo);
@@ -1350,22 +1372,33 @@
      * {@link DreamService#DEFAULT_SHOW_COMPLICATIONS}.
      */
     private static boolean fetchShouldShowComplications(Context context,
-            ComponentName componentName) {
+            @Nullable ServiceInfo serviceInfo) {
+        final DreamMetadata metadata = getDreamMetadata(context, serviceInfo);
+        if (metadata != null) {
+            return metadata.showComplications;
+        }
+        return DEFAULT_SHOW_COMPLICATIONS;
+    }
+
+    @Nullable
+    private static CharSequence fetchDreamLabel(Context context,
+            @Nullable ServiceInfo serviceInfo) {
+        if (serviceInfo == null) return null;
+        final PackageManager pm = context.getPackageManager();
+        return serviceInfo.loadLabel(pm);
+    }
+
+    @Nullable
+    private static ServiceInfo fetchServiceInfo(Context context, ComponentName componentName) {
         final PackageManager pm = context.getPackageManager();
 
         try {
-            final ServiceInfo si = pm.getServiceInfo(componentName,
+            return pm.getServiceInfo(componentName,
                     PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA));
-            final DreamMetadata metadata = getDreamMetadata(context, si);
-
-            if (metadata != null) {
-                return metadata.showComplications;
-            }
         } catch (PackageManager.NameNotFoundException e) {
             if (DEBUG) Log.w(TAG, "cannot find component " + componentName.flattenToShortString());
         }
-
-        return DEFAULT_SHOW_COMPLICATIONS;
+        return null;
     }
 
     @Override
diff --git a/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java b/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java
index 8fe6f71..adc9251 100644
--- a/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java
+++ b/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java
@@ -40,7 +40,7 @@
 
     private final IBinder mTargetInputToken;
     private final SelectionToolbarRenderService.TransferTouchListener mTransferTouchListener;
-    private Rect mContentRect;
+    private final Rect mContentRect = new Rect();
 
     private int mLastDownX = -1;
     private int mLastDownY = -1;
@@ -57,7 +57,7 @@
      * Sets the Rect that shows the selection toolbar content.
      */
     public void setContentRect(Rect contentRect) {
-        mContentRect = contentRect;
+        mContentRect.set(contentRect);
     }
 
     @Override
diff --git a/core/java/android/service/wallpapereffectsgeneration/WallpaperEffectsGenerationService.java b/core/java/android/service/wallpapereffectsgeneration/WallpaperEffectsGenerationService.java
index 18b654e..2898149 100644
--- a/core/java/android/service/wallpapereffectsgeneration/WallpaperEffectsGenerationService.java
+++ b/core/java/android/service/wallpapereffectsgeneration/WallpaperEffectsGenerationService.java
@@ -19,6 +19,7 @@
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.annotation.CallSuper;
+import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.app.Service;
@@ -69,7 +70,6 @@
      * {@link android.permission#MANAGE_WALLPAPER_EFFECTS_GENERATION}
      * permission.
      *
-     * @hide
      */
     public static final String SERVICE_INTERFACE =
             "android.service.wallpapereffectsgeneration.WallpaperEffectsGenerationService";
@@ -97,6 +97,7 @@
      *
      * @param request the cinematic effect request passed from the client.
      */
+    @MainThread
     public abstract void onGenerateCinematicEffect(@NonNull CinematicEffectRequest request);
 
     /**
diff --git a/core/java/android/util/DumpableContainer.java b/core/java/android/util/DumpableContainer.java
index e7b2acd..fef5acd 100644
--- a/core/java/android/util/DumpableContainer.java
+++ b/core/java/android/util/DumpableContainer.java
@@ -18,8 +18,7 @@
 import android.annotation.NonNull;
 
 /**
- * Objects that contains a list of {@link Dumpable}, which will be dumped when the object itself
- * is dumped.
+ * Represents a container that manages {@link Dumpable dumpables}.
  */
 public interface DumpableContainer {
 
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 188d745..7d56039 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -737,15 +737,47 @@
     }
 
     /**
-     * Gets the key code produced by the specified location on a US keyboard layout.
-     * Key code as defined in {@link android.view.KeyEvent}.
-     * This API is only functional for devices with {@link InputDevice#SOURCE_KEYBOARD} available
-     * which can alter their key mapping using country specific keyboard layouts.
+     * Gets the {@link android.view.KeyEvent key code} produced by the given location on a reference
+     * QWERTY keyboard layout.
+     * <p>
+     * This API is useful for querying the physical location of keys that change the character
+     * produced based on the current locale and keyboard layout.
+     * <p>
+     * The following table provides a non-exhaustive list of examples:
+     * <table border="2" width="85%" align="center" cellpadding="5">
+     *     <thead>
+     *         <tr><th>Active Keyboard Layout</th> <th>Input Parameter</th>
+     *         <th>Return Value</th></tr>
+     *     </thead>
      *
-     * @param locationKeyCode The location of a key on a US keyboard layout.
-     * @return The key code produced when pressing the key at the specified location, given the
-     *         active keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the requested
-     *         mapping could not be determined, or if an error occurred.
+     *     <tbody>
+     *     <tr>
+     *         <td>French AZERTY</td>
+     *         <td><code>{@link KeyEvent#KEYCODE_Q}</code></td>
+     *         <td><code>{@link KeyEvent#KEYCODE_A}</code></td>
+     *     </tr>
+     *     <tr>
+     *         <td>German QWERTZ</td>
+     *         <td><code>{@link KeyEvent#KEYCODE_Y}</code></td>
+     *         <td><code>{@link KeyEvent#KEYCODE_Z}</code></td>
+     *     </tr>
+     *     <tr>
+     *         <td>US QWERTY</td>
+     *         <td><code>{@link KeyEvent#KEYCODE_B}</code></td>
+     *         <td><code>{@link KeyEvent#KEYCODE_B}</code></td>
+     *     </tr>
+     *     </tbody>
+     * </table>
+     *
+     * @param locationKeyCode The location of a key specified as a key code on the QWERTY layout.
+     * This provides a consistent way of referring to the physical location of a key independently
+     * of the current keyboard layout. Also see the
+     * <a href="https://www.w3.org/TR/2017/CR-uievents-code-20170601/#key-alphanumeric-writing-system">
+     * hypothetical keyboard</a> provided by the W3C, which may be helpful for identifying the
+     * physical location of a key.
+     * @return The key code produced by the key at the specified location, given the current
+     * keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the device does not specify
+     * {@link InputDevice#SOURCE_KEYBOARD} or the requested mapping cannot be determined.
      */
     public int getKeyCodeForKeyLocation(int locationKeyCode) {
         return InputManager.getInstance().getKeyCodeForKeyLocation(mId, locationKeyCode);
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 98cef958..632af23 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -3094,6 +3094,10 @@
          * @param destFrame The destination rectangle in parent space. Or null for the source frame.
          * @param orientation The buffer rotation
          * @return This transaction object.
+         * @deprecated Use {@link #setCrop(SurfaceControl, Rect)},
+         * {@link #setBufferTransform(SurfaceControl, int)},
+         * {@link #setPosition(SurfaceControl, float, float)} and
+         * {@link #setScale(SurfaceControl, float, float)} instead.
          */
         @NonNull
         public Transaction setGeometry(@NonNull SurfaceControl sc, @Nullable Rect sourceCrop,
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 2edfda5..a13579d 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -328,7 +328,8 @@
      */
     public @Nullable SurfacePackage getSurfacePackage() {
         if (mSurfaceControl != null && mAccessibilityEmbeddedConnection != null) {
-            return new SurfacePackage(mSurfaceControl, mAccessibilityEmbeddedConnection,
+            return new SurfacePackage(new SurfaceControl(mSurfaceControl, "getSurfacePackage"),
+                mAccessibilityEmbeddedConnection,
                 mWm.getFocusGrantToken(), mRemoteInterface);
         } else {
             return null;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 286b502..34a1386 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -14173,7 +14173,7 @@
                 && isAccessibilityPane()) {
             // If the pane isn't visible, content changed events are sufficient unless we're
             // reporting that the view just disappeared
-            if ((getVisibility() == VISIBLE)
+            if ((isAggregatedVisible())
                     || (changeType == AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED)) {
                 final AccessibilityEvent event = AccessibilityEvent.obtain();
                 onInitializeAccessibilityEvent(event);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8444032..03f05f2 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -76,6 +76,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
@@ -217,6 +218,7 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.lang.ref.WeakReference;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.LinkedList;
@@ -2460,8 +2462,9 @@
             if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
                     + ", desiredWindowWidth=" + desiredWindowWidth);
             if (baseSize != 0 && desiredWindowWidth > baseSize) {
-                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
-                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
+                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width, lp.privateFlags);
+                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height,
+                        lp.privateFlags);
                 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                 if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
                         + host.getMeasuredWidth() + "," + host.getMeasuredHeight()
@@ -2474,7 +2477,7 @@
                     baseSize = (baseSize+desiredWindowWidth)/2;
                     if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="
                             + baseSize);
-                    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
+                    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width, lp.privateFlags);
                     performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                     if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
                             + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
@@ -2487,8 +2490,10 @@
         }
 
         if (!goodMeasure) {
-            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
-            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
+            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width,
+                    lp.privateFlags);
+            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height,
+                    lp.privateFlags);
             performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
             if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                 windowSizeMayChange = true;
@@ -2721,6 +2726,10 @@
         // Execute enqueued actions on every traversal in case a detached view enqueued an action
         getRunQueue().executeActions(mAttachInfo.mHandler);
 
+        if (mApplyInsetsRequested && !(mWillMove || mWillResize)) {
+            dispatchApplyInsets(host);
+        }
+
         boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
         if (layoutRequested) {
 
@@ -2785,18 +2794,6 @@
             }
         }
 
-        if (mApplyInsetsRequested && !(mWillMove || mWillResize)) {
-            dispatchApplyInsets(host);
-            if (mLayoutRequested) {
-                // Short-circuit catching a new layout request here, so
-                // we don't need to go through two layout passes when things
-                // change due to fitting system windows, which can happen a lot.
-                windowSizeMayChange |= measureHierarchy(host, lp,
-                        mView.getContext().getResources(),
-                        desiredWindowWidth, desiredWindowHeight);
-            }
-        }
-
         if (layoutRequested) {
             // Clear this now, so that if anything requests a layout in the
             // rest of this function we will catch it and re-run a full
@@ -3156,8 +3153,10 @@
                 if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                         || mHeight != host.getMeasuredHeight() || dispatchApplyInsets ||
                         updatedConfiguration) {
-                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
-                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
+                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width,
+                            lp.privateFlags);
+                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height,
+                            lp.privateFlags);
 
                     if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed!  mWidth="
                             + mWidth + " measuredWidth=" + host.getMeasuredWidth()
@@ -3693,7 +3692,7 @@
             return current;
         }
 
-        final Queue<AccessibilityNodeInfo> fringe = new LinkedList<>();
+        final Queue<AccessibilityNodeInfo> fringe = new ArrayDeque<>();
         fringe.offer(current);
 
         while (!fringe.isEmpty()) {
@@ -3957,31 +3956,28 @@
      * Figures out the measure spec for the root view in a window based on it's
      * layout params.
      *
-     * @param windowSize
-     *            The available width or height of the window
-     *
-     * @param rootDimension
-     *            The layout params for one dimension (width or height) of the
-     *            window.
-     *
+     * @param windowSize The available width or height of the window.
+     * @param measurement The layout width or height requested in the layout params.
+     * @param privateFlags The private flags in the layout params of the window.
      * @return The measure spec to use to measure the root view.
      */
-    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
+    private static int getRootMeasureSpec(int windowSize, int measurement, int privateFlags) {
         int measureSpec;
+        final int rootDimension = (privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0
+                ? MATCH_PARENT : measurement;
         switch (rootDimension) {
-
-        case ViewGroup.LayoutParams.MATCH_PARENT:
-            // Window can't resize. Force root view to be windowSize.
-            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
-            break;
-        case ViewGroup.LayoutParams.WRAP_CONTENT:
-            // Window can resize. Set max size for root view.
-            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
-            break;
-        default:
-            // Window wants to be an exact size. Force root view to be that size.
-            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
-            break;
+            case ViewGroup.LayoutParams.MATCH_PARENT:
+                // Window can't resize. Force root view to be windowSize.
+                measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
+                break;
+            case ViewGroup.LayoutParams.WRAP_CONTENT:
+                // Window can resize. Set max size for root view.
+                measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
+                break;
+            default:
+                // Window wants to be an exact size. Force root view to be that size.
+                measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
+                break;
         }
         return measureSpec;
     }
diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java
index 27f89ec..ad9f21b 100644
--- a/core/java/android/view/WindowLayout.java
+++ b/core/java/android/view/WindowLayout.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.view.Gravity.DISPLAY_CLIP_HORIZONTAL;
+import static android.view.Gravity.DISPLAY_CLIP_VERTICAL;
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
@@ -29,6 +31,7 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
@@ -250,11 +253,39 @@
         Gravity.apply(attrs.gravity, w, h, outParentFrame,
                 (int) (x + attrs.horizontalMargin * pw),
                 (int) (y + attrs.verticalMargin * ph), outFrame);
+
         // Now make sure the window fits in the overall display frame.
         if (fitToDisplay) {
             Gravity.applyDisplay(attrs.gravity, outDisplayFrame, outFrame);
         }
 
+        if ((attrs.privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0
+                && !cutout.isEmpty()) {
+            // If the actual frame covering a display cutout, and the window is requesting to extend
+            // it's requested frame, re-do the frame calculation after getting the new requested
+            // size.
+            mTempRect.set(outFrame);
+            // Do nothing if the display cutout and window don't overlap entirely. This may happen
+            // when the cutout is not on the same side with the window.
+            boolean shouldExpand = false;
+            final Rect [] cutoutBounds = cutout.getBoundingRectsAll();
+            for (Rect cutoutBound : cutoutBounds) {
+                if (cutoutBound.isEmpty()) continue;
+                if (mTempRect.contains(cutoutBound) || cutoutBound.contains(mTempRect)) {
+                    shouldExpand = true;
+                    break;
+                }
+            }
+            if (shouldExpand) {
+                // Try to fit move the bar to avoid the display cutout first. Make sure the clip
+                // flags are not set to make the window move.
+                final int clipFlags = DISPLAY_CLIP_VERTICAL | DISPLAY_CLIP_HORIZONTAL;
+                Gravity.applyDisplay(attrs.gravity & ~clipFlags, displayCutoutSafe,
+                        mTempRect);
+                outFrame.union(mTempRect);
+            }
+        }
+
         if (DEBUG) Log.d(TAG, "computeWindowFrames " + attrs.getTitle()
                 + " outFrame=" + outFrame.toShortString()
                 + " outParentFrame=" + outParentFrame.toShortString()
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 1c27046..dbfae46 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2385,6 +2385,16 @@
         public static final int PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR = 0x00001000;
 
         /**
+         * Flag to indicate that the window frame should be the requested frame adding the display
+         * cutout frame. This will only be applied if a specific size smaller than the parent frame
+         * is given, and the window is covering the display cutout. The extended frame will not be
+         * larger than the parent frame.
+         *
+         * {@hide}
+         */
+        public static final int PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT = 0x00002000;
+
+        /**
          * Flag that will make window ignore app visibility and instead depend purely on the decor
          * view visibility for determining window visibility. This is used by recents to keep
          * drawing after it launches an app.
@@ -3222,7 +3232,8 @@
 
         /**
          * The window is allowed to extend into the {@link DisplayCutout} area, only if the
-         * {@link DisplayCutout} is fully contained within a system bar. Otherwise, the window is
+         * {@link DisplayCutout} is fully contained within a system bar or the {@link DisplayCutout}
+         * is not deeper than 16 dp, but this depends on the OEM choice. Otherwise, the window is
          * laid out such that it does not overlap with the {@link DisplayCutout} area.
          *
          * <p>
@@ -3237,6 +3248,13 @@
          * The usual precautions for not overlapping with the status and navigation bar are
          * sufficient for ensuring that no important content overlaps with the DisplayCutout.
          *
+         * <p>
+         * Note: OEMs can have an option to allow the window to always extend into the
+         * {@link DisplayCutout} area, no matter the cutout flag set, when the {@link DisplayCutout}
+         * is on the different side from system bars, only if the {@link DisplayCutout} overlaps at
+         * most 16dp with the windows.
+         * In such case, OEMs must provide an opt-in/out affordance for users.
+         *
          * @see DisplayCutout
          * @see WindowInsets
          * @see #layoutInDisplayCutoutMode
@@ -3249,8 +3267,16 @@
          * The window is always allowed to extend into the {@link DisplayCutout} areas on the short
          * edges of the screen.
          *
+         * <p>
          * The window will never extend into a {@link DisplayCutout} area on the long edges of the
-         * screen.
+         * screen, unless the {@link DisplayCutout} is not deeper than 16 dp, but this depends on
+         * the OEM choice.
+         *
+         * <p>
+         * Note: OEMs can have an option to allow the window to extend into the
+         * {@link DisplayCutout} area on the long edge side, only if the cutout overlaps at most
+         * 16dp with the windows. In such case, OEMs must provide an opt-in/out affordance for
+         * users.
          *
          * <p>
          * The window must make sure that no important content overlaps with the
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 07b7a18..6853278 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -46,11 +46,11 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Queue;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -1166,7 +1166,7 @@
                         + connectionIdWaitingForPrefetchResultCopy + ";Result: " + infos,
                         Binder.getCallingUid(),
                         Arrays.asList(Thread.currentThread().getStackTrace()),
-                        new HashSet<String>(Arrays.asList("getStackTrace")),
+                        new HashSet<>(Collections.singletonList("getStackTrace")),
                         FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK);
             }
         } else if (DEBUG) {
@@ -1348,7 +1348,7 @@
         }
         // Check for duplicates.
         HashSet<AccessibilityNodeInfo> seen = new HashSet<>();
-        Queue<AccessibilityNodeInfo> fringe = new LinkedList<>();
+        Queue<AccessibilityNodeInfo> fringe = new ArrayDeque<>();
         fringe.add(root);
         while (!fringe.isEmpty()) {
             AccessibilityNodeInfo current = fringe.poll();
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index aeef76c..eadec03 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -4487,8 +4487,8 @@
             case R.id.accessibilityActionDragDrop:
                 return "ACTION_DROP";
             default: {
-                if (action == R.id.accessibilityActionShowSuggestions) {
-                    return "ACTION_SHOW_SUGGESTIONS";
+                if (action == R.id.accessibilityActionShowTextSuggestions) {
+                    return "ACTION_SHOW_TEXT_SUGGESTIONS";
                 }
                 return "ACTION_UNKNOWN";
             }
@@ -5215,8 +5215,8 @@
         /**
          * Action to show suggestions for editable text.
          */
-        @NonNull public static final AccessibilityAction ACTION_SHOW_SUGGESTIONS =
-                new AccessibilityAction(R.id.accessibilityActionShowSuggestions);
+        @NonNull public static final AccessibilityAction ACTION_SHOW_TEXT_SUGGESTIONS =
+                new AccessibilityAction(R.id.accessibilityActionShowTextSuggestions);
 
         private final int mActionId;
         private final CharSequence mLabel;
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 2717463..017b8aa 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -46,6 +46,8 @@
 import android.annotation.TestApi;
 import android.annotation.UserIdInt;
 import android.app.ActivityThread;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -356,6 +358,21 @@
     /** @hide */
     public static final int SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES = 2;
 
+    /**
+     * Clear {@link #SHOW_FORCED} flag when the next IME focused application changed.
+     *
+     * <p>
+     * Note that when this flag enabled in server side, {@link #SHOW_FORCED} will no longer
+     * affect the next focused application to keep showing IME, in case of unexpected IME visible
+     * when the next focused app isn't be the IME requester. </p>
+     *
+     * @hide
+     */
+    @TestApi
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // This is a bug id.
+
     @UnsupportedAppUsage
     final IInputMethodManager mService;
     final Looper mMainLooper;
@@ -1646,7 +1663,14 @@
      * Flag for {@link #showSoftInput} to indicate that the user has forced
      * the input method open (such as by long-pressing menu) so it should
      * not be closed until they explicitly do so.
+     *
+     * @deprecated Use {@link #showSoftInput} without this flag instead. Using this flag can lead
+     * to the soft input remaining visible even when the calling application is closed. The
+     * use of this flag can make the soft input remains visible globally. Starting in
+     * {@link Build.VERSION_CODES#TIRAMISU Android T}, this flag only has an effect while the
+     * caller is currently focused.
      */
+    @Deprecated
     public static final int SHOW_FORCED = 0x0002;
 
     /**
diff --git a/core/java/android/view/textservice/SpellCheckerSession.java b/core/java/android/view/textservice/SpellCheckerSession.java
index 6cfb938..5d9f4f6 100644
--- a/core/java/android/view/textservice/SpellCheckerSession.java
+++ b/core/java/android/view/textservice/SpellCheckerSession.java
@@ -39,7 +39,7 @@
 
 import dalvik.system.CloseGuard;
 
-import java.util.LinkedList;
+import java.util.ArrayDeque;
 import java.util.Locale;
 import java.util.Queue;
 import java.util.concurrent.Executor;
@@ -245,7 +245,7 @@
             }
         }
 
-        private final Queue<SpellCheckerParams> mPendingTasks = new LinkedList<>();
+        private final Queue<SpellCheckerParams> mPendingTasks = new ArrayDeque<>();
         @GuardedBy("SpellCheckerSessionListenerImpl.this")
         private SpellCheckerSession mSpellCheckerSession;
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index dbaf0aa..3c8fcb9 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -12335,7 +12335,8 @@
                 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
             }
             if (canReplace()) {
-                info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_SUGGESTIONS);
+                info.addAction(
+                        AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_TEXT_SUGGESTIONS);
             }
             if (canShare()) {
                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
@@ -12656,7 +12657,7 @@
             default: {
                 // New ids have static blocks to assign values, so they can't be used in a case
                 // block.
-                if (action == R.id.accessibilityActionShowSuggestions) {
+                if (action == R.id.accessibilityActionShowTextSuggestions) {
                     return isFocused() && canReplace() && onTextContextMenuItem(ID_REPLACE);
                 }
                 return super.performAccessibilityActionInternal(action, arguments);
@@ -12796,7 +12797,9 @@
     /**
      * Called when a context menu option for the text view is selected.  Currently
      * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
-     * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
+     * {@link android.R.id#copy}, {@link android.R.id#paste},
+     * {@link android.R.id#pasteAsPlainText} (starting at API level 23) or
+     * {@link android.R.id#shareText}.
      *
      * @return true if the context menu item action was performed.
      */
@@ -12987,6 +12990,7 @@
      * method. The default actions can also be removed from the menu using
      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
      * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
+     * {@link android.R.id#pasteAsPlainText} (starting at API level 23),
      * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
      *
      * <p>Returning false from
@@ -13025,7 +13029,8 @@
      * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
      * android.view.Menu)} method. The default actions can also be removed from the menu using
      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
-     * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
+     * {@link android.R.id#paste}, {@link android.R.id#pasteAsPlainText} (starting at API
+     * level 23) or {@link android.R.id#replaceText} ids as parameters.</p>
      *
      * <p>Returning false from
      * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index cdb69e5..e336e96 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -33,7 +33,6 @@
 import android.annotation.UiThread;
 import android.app.Activity;
 import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
 import android.app.ActivityThread;
 import android.app.VoiceInteractor.PickOptionRequest;
 import android.app.VoiceInteractor.PickOptionRequest.Option;
@@ -54,13 +53,11 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Insets;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.IBinder;
 import android.os.PatternMatcher;
 import android.os.RemoteException;
 import android.os.StrictMode;
@@ -1464,36 +1461,9 @@
 
     public boolean startAsCallerImpl(Intent intent, Bundle options, boolean ignoreTargetSecurity,
             int userId) {
-        // Pass intent to delegate chooser activity with permission token.
-        // TODO: This should move to a trampoline Activity in the system when the ChooserActivity
-        // moves into systemui
-        try {
-            // TODO: Once this is a small springboard activity, it can move off the UI process
-            // and we can move the request method to ActivityManagerInternal.
-            final Intent chooserIntent = new Intent();
-            final ComponentName delegateActivity = ComponentName.unflattenFromString(
-                    Resources.getSystem().getString(R.string.config_chooserActivity));
-            IBinder permissionToken = ActivityTaskManager.getService()
-                    .requestStartActivityPermissionToken(delegateActivity);
-            chooserIntent.setClassName(delegateActivity.getPackageName(),
-                    delegateActivity.getClassName());
-            chooserIntent.putExtra(ActivityTaskManager.EXTRA_PERMISSION_TOKEN, permissionToken);
-
-            // TODO: These extras will change as chooser activity moves into systemui
-            chooserIntent.putExtra(Intent.EXTRA_INTENT, intent);
-            chooserIntent.putExtra(ActivityTaskManager.EXTRA_OPTIONS, options);
-            chooserIntent.putExtra(ActivityTaskManager.EXTRA_IGNORE_TARGET_SECURITY,
-                    ignoreTargetSecurity);
-            chooserIntent.putExtra(Intent.EXTRA_USER_ID, userId);
-            chooserIntent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
-
-            // Don't close until the delegate finishes, or the token will be invalidated.
-            mAwaitingDelegateResponse = true;
-
-            startActivityForResult(chooserIntent, REQUEST_CODE_RETURN_FROM_DELEGATE_CHOOSER);
-        } catch (RemoteException e) {
-            Log.e(TAG, e.toString());
-        }
+        // Note: this method will be overridden in the delegate implementation to use the passed-in
+        // permission token.
+        startActivityAsCaller(intent, options, null, false, userId);
         return true;
     }
 
diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
index 301de2d..dc53e77 100644
--- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
+++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
@@ -30,11 +30,9 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
-import android.provider.DeviceConfig;
 
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -45,11 +43,6 @@
  * resolve it to an activity.
  */
 public class DisplayResolveInfo implements TargetInfo, Parcelable {
-    private final boolean mEnableChooserDelegate =
-            DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                    SystemUiDeviceConfigFlags.USE_DELEGATE_CHOOSER,
-                    false);
-
     private final ResolveInfo mResolveInfo;
     private CharSequence mDisplayLabel;
     private Drawable mDisplayIcon;
@@ -180,12 +173,9 @@
 
     @Override
     public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
-        if (mEnableChooserDelegate) {
-            return activity.startAsCallerImpl(mResolvedIntent, options, false, userId);
-        } else {
-            activity.startActivityAsCaller(mResolvedIntent, options, null, false, userId);
-            return true;
-        }
+        // TODO: if the start-as-caller API no longer requires a permission token, this can go back
+        // to inlining the real activity-start call, and we can remove startAsCallerImpl.
+        return activity.startAsCallerImpl(mResolvedIntent, options, false, userId);
     }
 
     @Override
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index a60b310..73a4f1c 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -62,7 +62,9 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Deque;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
@@ -232,9 +234,9 @@
             throw new FileNotFoundException(doc + " is not found under " + parent);
         }
 
-        LinkedList<String> path = new LinkedList<>();
+        List<String> path = new ArrayList<>();
         while (doc != null && FileUtils.contains(parent, doc)) {
-            path.addFirst(getDocIdForFile(doc));
+            path.add(0, getDocIdForFile(doc));
 
             doc = doc.getParentFile();
         }
@@ -448,10 +450,10 @@
             File folder, String[] projection, Set<String> exclusion, Bundle queryArgs)
             throws FileNotFoundException {
         final MatrixCursor result = new MatrixCursor(resolveProjection(projection));
-        final LinkedList<File> pending = new LinkedList<>();
+        final List<File> pending = new ArrayList<>();
         pending.add(folder);
         while (!pending.isEmpty() && result.getCount() < 24) {
-            final File file = pending.removeFirst();
+            final File file = pending.remove(0);
             if (shouldHide(file)) continue;
 
             if (file.isDirectory()) {
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 55c2f4c..692bca6 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -166,7 +166,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    static final int VERSION = 206;
+    static final int VERSION = 207;
 
     // The maximum number of names wakelocks we will keep track of
     // per uid; once the limit is reached, we batch the remaining wakelocks
@@ -964,6 +964,16 @@
          * Timers for each combination of frequency range and signal strength.
          */
         public final StopwatchTimer[][] perStateTimers;
+        /**
+         * Counters tracking the time (in milliseconds) spent transmitting data in a given state.
+         */
+        @Nullable
+        private LongSamplingCounter[][] mPerStateTxDurationMs = null;
+        /**
+         * Counters tracking the time (in milliseconds) spent receiving data in at given frequency.
+         */
+        @Nullable
+        private LongSamplingCounter[] mPerFrequencyRxDurationMs = null;
 
         RadioAccessTechnologyBatteryStats(int freqCount, Clock clock, TimeBase timeBase) {
             perStateTimers =
@@ -1024,15 +1034,198 @@
         }
 
         /**
-         * Reset display timers.
+         * Returns the duration in milliseconds spent in a given state since the last mark.
+         */
+        public long getTimeSinceMark(@ServiceState.FrequencyRange int frequencyRange,
+                int signalStrength, long elapsedRealtimeMs) {
+            return perStateTimers[frequencyRange][signalStrength].getTimeSinceMarkLocked(
+                    elapsedRealtimeMs * 1000) / 1000;
+        }
+
+        /**
+         * Set mark for all timers.
+         */
+        public void setMark(long elapsedRealtimeMs) {
+            final int size = perStateTimers.length;
+            for (int i = 0; i < size; i++) {
+                for (int j = 0; j < CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS; j++) {
+                    perStateTimers[i][j].setMark(elapsedRealtimeMs);
+                }
+            }
+        }
+
+        /**
+         * Returns numbers of frequencies tracked for this RAT.
+         */
+        public int getFrequencyRangeCount() {
+            return perStateTimers.length;
+        }
+
+        /**
+         * Add TX time for a given state.
+         */
+        public void incrementTxDuration(@ServiceState.FrequencyRange int frequencyRange,
+                int signalStrength, long durationMs) {
+            getTxDurationCounter(frequencyRange, signalStrength, true).addCountLocked(durationMs);
+        }
+
+        /**
+         * Add TX time for a given frequency.
+         */
+        public void incrementRxDuration(@ServiceState.FrequencyRange int frequencyRange,
+                long durationMs) {
+            getRxDurationCounter(frequencyRange, true).addCountLocked(durationMs);
+        }
+
+        /**
+         * Reset radio access technology timers and counts.
          */
         public void reset(long elapsedRealtimeUs) {
             final int size = perStateTimers.length;
             for (int i = 0; i < size; i++) {
                 for (int j = 0; j < CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS; j++) {
                     perStateTimers[i][j].reset(false, elapsedRealtimeUs);
+                    if (mPerStateTxDurationMs == null) continue;
+                    mPerStateTxDurationMs[i][j].reset(false, elapsedRealtimeUs);
+                }
+                if (mPerFrequencyRxDurationMs == null) continue;
+                mPerFrequencyRxDurationMs[i].reset(false, elapsedRealtimeUs);
+            }
+        }
+
+        /**
+         * Write data to summary parcel
+         */
+        public void writeSummaryToParcel(Parcel out, long elapsedRealtimeUs) {
+            final int freqCount = perStateTimers.length;
+            out.writeInt(freqCount);
+            out.writeInt(CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS);
+            for (int i = 0; i < freqCount; i++) {
+                for (int j = 0; j < CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS; j++) {
+                    perStateTimers[i][j].writeSummaryFromParcelLocked(out, elapsedRealtimeUs);
                 }
             }
+
+            if (mPerStateTxDurationMs == null) {
+                out.writeInt(0);
+            } else {
+                out.writeInt(1);
+                for (int i = 0; i < freqCount; i++) {
+                    for (int j = 0; j < CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS; j++) {
+                        mPerStateTxDurationMs[i][j].writeSummaryFromParcelLocked(out);
+                    }
+                }
+            }
+
+            if (mPerFrequencyRxDurationMs == null) {
+                out.writeInt(0);
+            } else {
+                out.writeInt(1);
+                for (int i = 0; i < freqCount; i++) {
+                    mPerFrequencyRxDurationMs[i].writeSummaryFromParcelLocked(out);
+                }
+            }
+        }
+
+        /**
+         * Read data from summary parcel
+         */
+        public void readSummaryFromParcel(Parcel in) {
+            final int oldFreqCount = in.readInt();
+            final int oldSignalStrengthCount = in.readInt();
+            final int currFreqCount = perStateTimers.length;
+            final int currSignalStrengthCount = CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS;
+
+            for (int freq = 0; freq < oldFreqCount; freq++) {
+                for (int strength = 0; strength < oldSignalStrengthCount; strength++) {
+                    if (freq >= currFreqCount || strength >= currSignalStrengthCount) {
+                        // Mismatch with the summary parcel. Consume the data but don't use it.
+                        final StopwatchTimer temp = new StopwatchTimer(null, null, -1, null,
+                                new TimeBase());
+                        // Consume perStateTimers data.
+                        temp.readSummaryFromParcelLocked(in);
+                    } else {
+                        perStateTimers[freq][strength].readSummaryFromParcelLocked(in);
+                    }
+                }
+            }
+
+            if (in.readInt() == 1) {
+                for (int freq = 0; freq < oldFreqCount; freq++) {
+                    for (int strength = 0; strength < oldSignalStrengthCount; strength++) {
+                        if (freq >= currFreqCount || strength >= currSignalStrengthCount) {
+                            // Mismatch with the summary parcel. Consume the data but don't use it.
+                            final StopwatchTimer temp = new StopwatchTimer(null, null, -1, null,
+                                    new TimeBase());
+                            // Consume mPerStateTxDurationMs data.
+                            temp.readSummaryFromParcelLocked(in);
+                        }
+                        getTxDurationCounter(freq, strength, true).readSummaryFromParcelLocked(in);
+                    }
+                }
+            }
+
+            if (in.readInt() == 1) {
+                for (int freq = 0; freq < oldFreqCount; freq++) {
+                    if (freq >= currFreqCount) {
+                        // Mismatch with the summary parcel. Consume the data but don't use it.
+                        final StopwatchTimer
+                                temp = new StopwatchTimer(null, null, -1, null, new TimeBase());
+                        // Consume mPerFrequencyRxDurationMs data.
+                        temp.readSummaryFromParcelLocked(in);
+                        continue;
+                    }
+                    getRxDurationCounter(freq, true).readSummaryFromParcelLocked(in);
+                }
+            }
+        }
+
+        private LongSamplingCounter getTxDurationCounter(
+                @ServiceState.FrequencyRange int frequencyRange, int signalStrength, boolean make) {
+            if (mPerStateTxDurationMs == null) {
+                if (!make) return null;
+
+                final int freqCount = getFrequencyRangeCount();
+                final int signalStrengthCount = perStateTimers[0].length;
+                final TimeBase timeBase = perStateTimers[0][0].mTimeBase;
+                mPerStateTxDurationMs = new LongSamplingCounter[freqCount][signalStrengthCount];
+                for (int freq = 0; freq < freqCount; freq++) {
+                    for (int strength = 0; strength < signalStrengthCount; strength++) {
+                        mPerStateTxDurationMs[freq][strength] = new LongSamplingCounter(timeBase);
+                    }
+                }
+            }
+            if (frequencyRange < 0 || frequencyRange >= getFrequencyRangeCount()) {
+                Slog.w(TAG, "Unexpected frequency range (" + frequencyRange
+                        + ") requested in getTxDurationCounter");
+                return null;
+            }
+            if (signalStrength < 0 || signalStrength >= perStateTimers[0].length) {
+                Slog.w(TAG, "Unexpected signal strength (" + signalStrength
+                        + ") requested in getTxDurationCounter");
+                return null;
+            }
+            return mPerStateTxDurationMs[frequencyRange][signalStrength];
+        }
+
+        private LongSamplingCounter getRxDurationCounter(
+                @ServiceState.FrequencyRange int frequencyRange, boolean make) {
+            if (mPerFrequencyRxDurationMs == null) {
+                if (!make) return null;
+
+                final int freqCount = getFrequencyRangeCount();
+                final TimeBase timeBase = perStateTimers[0][0].mTimeBase;
+                mPerFrequencyRxDurationMs = new LongSamplingCounter[freqCount];
+                for (int freq = 0; freq < freqCount; freq++) {
+                    mPerFrequencyRxDurationMs[freq] = new LongSamplingCounter(timeBase);
+                }
+            }
+            if (frequencyRange < 0 || frequencyRange >= getFrequencyRangeCount()) {
+                Slog.w(TAG, "Unexpected frequency range (" + frequencyRange
+                        + ") requested in getRxDurationCounter");
+                return null;
+            }
+            return mPerFrequencyRxDurationMs[frequencyRange];
         }
     }
 
@@ -8004,6 +8197,32 @@
                 elapsedRealtimeMs * 1000, STATS_SINCE_CHARGED) / 1000;
     }
 
+    @Override
+    public long getActiveTxRadioDurationMs(@RadioAccessTechnology int rat,
+            @ServiceState.FrequencyRange int frequencyRange, int signalStrength,
+            long elapsedRealtimeMs) {
+        final RadioAccessTechnologyBatteryStats stats = mPerRatBatteryStats[rat];
+        if (stats == null) return DURATION_UNAVAILABLE;
+
+        final LongSamplingCounter counter = stats.getTxDurationCounter(frequencyRange,
+                signalStrength, false);
+        if (counter == null) return DURATION_UNAVAILABLE;
+
+        return counter.getCountLocked(STATS_SINCE_CHARGED);
+    }
+
+    @Override
+    public long getActiveRxRadioDurationMs(@RadioAccessTechnology int rat,
+            @ServiceState.FrequencyRange int frequencyRange, long elapsedRealtimeMs) {
+        final RadioAccessTechnologyBatteryStats stats = mPerRatBatteryStats[rat];
+        if (stats == null) return DURATION_UNAVAILABLE;
+
+        final LongSamplingCounter counter = stats.getRxDurationCounter(frequencyRange, false);
+        if (counter == null) return DURATION_UNAVAILABLE;
+
+        return counter.getCountLocked(STATS_SINCE_CHARGED);
+    }
+
     @UnsupportedAppUsage
     @Override public long getMobileRadioActiveTime(long elapsedRealtimeUs, int which) {
         return mMobileRadioActiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
@@ -13486,6 +13705,67 @@
                     addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
                     mTmpRailStats.resetCellularTotalEnergyUsed();
                 }
+
+                // Proportionally smear Rx and Tx times across each RAt
+                final int levelCount = CellSignalStrength.getNumSignalStrengthLevels();
+                long[] perSignalStrengthActiveTimeMs = new long[levelCount];
+                long totalActiveTimeMs = 0;
+
+                for (int rat = 0; rat < RADIO_ACCESS_TECHNOLOGY_COUNT; rat++) {
+                    final RadioAccessTechnologyBatteryStats ratStats = mPerRatBatteryStats[rat];
+                    if (ratStats == null) continue;
+
+                    final int freqCount = ratStats.getFrequencyRangeCount();
+                    for (int freq = 0; freq < freqCount; freq++) {
+                        for (int level = 0; level < levelCount; level++) {
+                            final long durationMs = ratStats.getTimeSinceMark(freq, level,
+                                    elapsedRealtimeMs);
+                            perSignalStrengthActiveTimeMs[level] += durationMs;
+                            totalActiveTimeMs += durationMs;
+                        }
+                    }
+                }
+
+                if (totalActiveTimeMs != 0) {
+                    // Smear the provided Tx/Rx durations across each RAT, frequency, and signal
+                    // strength.
+                    for (int rat = 0; rat < RADIO_ACCESS_TECHNOLOGY_COUNT; rat++) {
+                        final RadioAccessTechnologyBatteryStats ratStats = mPerRatBatteryStats[rat];
+                        if (ratStats == null) continue;
+
+                        final int freqCount = ratStats.getFrequencyRangeCount();
+                        for (int freq = 0; freq < freqCount; freq++) {
+                            long frequencyDurationMs = 0;
+                            for (int level = 0; level < levelCount; level++) {
+                                final long durationMs = ratStats.getTimeSinceMark(freq, level,
+                                        elapsedRealtimeMs);
+                                final long totalLvlDurationMs =
+                                        perSignalStrengthActiveTimeMs[level];
+                                if (totalLvlDurationMs == 0) continue;
+                                final long totalTxLvlDurations =
+                                        deltaInfo.getTransmitDurationMillisAtPowerLevel(level);
+                                // Smear HAL provided Tx power level duration based on active modem
+                                // duration in a given state. (Add totalLvlDurationMs / 2 before
+                                // the integer division with totalLvlDurationMs for rounding.)
+                                final long proportionalTxDurationMs =
+                                        (durationMs * totalTxLvlDurations
+                                                + (totalLvlDurationMs / 2)) / totalLvlDurationMs;
+                                ratStats.incrementTxDuration(freq, level, proportionalTxDurationMs);
+                                frequencyDurationMs += durationMs;
+                            }
+                            final long totalRxDuration = deltaInfo.getReceiveTimeMillis();
+                            // Smear HAL provided Rx power duration based on active modem
+                            // duration in a given state.  (Add totalActiveTimeMs / 2 before the
+                            // integer division with totalActiveTimeMs for rounding.)
+                            final long proportionalRxDurationMs =
+                                    (frequencyDurationMs * totalRxDuration + (totalActiveTimeMs
+                                            / 2)) / totalActiveTimeMs;
+                            ratStats.incrementRxDuration(freq, proportionalRxDurationMs);
+                        }
+
+                        ratStats.setMark(elapsedRealtimeMs);
+                    }
+                }
             }
             long totalAppRadioTimeUs = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked(
                     elapsedRealtimeMs * 1000);
@@ -16927,6 +17207,13 @@
             mNetworkByteActivityCounters[i].readSummaryFromParcelLocked(in);
             mNetworkPacketActivityCounters[i].readSummaryFromParcelLocked(in);
         }
+
+        final int numRat = in.readInt();
+        for (int i = 0; i < numRat; i++) {
+            if (in.readInt() == 0) continue;
+            getRatBatteryStatsLocked(i).readSummaryFromParcel(in);
+        }
+
         mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
         mMobileRadioActiveTimer.readSummaryFromParcelLocked(in);
         mMobileRadioActivePerAppTimer.readSummaryFromParcelLocked(in);
@@ -17432,6 +17719,17 @@
             mNetworkByteActivityCounters[i].writeSummaryFromParcelLocked(out);
             mNetworkPacketActivityCounters[i].writeSummaryFromParcelLocked(out);
         }
+        final int numRat = mPerRatBatteryStats.length;
+        out.writeInt(numRat);
+        for (int i = 0; i < numRat; i++) {
+            final RadioAccessTechnologyBatteryStats ratStat = mPerRatBatteryStats[i];
+            if (ratStat == null) {
+                out.writeInt(0);
+                continue;
+            }
+            out.writeInt(1);
+            ratStat.writeSummaryToParcel(out, nowRealtime);
+        }
         mMobileRadioActiveTimer.writeSummaryFromParcelLocked(out, nowRealtime);
         mMobileRadioActivePerAppTimer.writeSummaryFromParcelLocked(out, nowRealtime);
         mMobileRadioActiveAdjustedTime.writeSummaryFromParcelLocked(out);
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 9bdcddf..1fd0410 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -888,6 +888,15 @@
         }
     }
 
+    /**
+     * Returns the {@code i}-th item in {@code items}, if it exists and {@code items} is not {@code
+     * null}, otherwise returns {@code null}.
+     */
+    @Nullable
+    public static <T> T getOrNull(@Nullable T[] items, int i) {
+        return (items != null && items.length > i) ? items[i] : null;
+    }
+
     public static @Nullable <T> T firstOrNull(T[] items) {
         return items.length > 0 ? items[0] : null;
     }
diff --git a/core/java/com/android/internal/view/menu/CascadingMenuPopup.java b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
index bf3e8d5..a0f7905 100644
--- a/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
@@ -1,11 +1,5 @@
 package com.android.internal.view.menu;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
 import android.annotation.AttrRes;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -22,16 +16,16 @@
 import android.view.LayoutInflater;
 import android.view.MenuItem;
 import android.view.View;
-import android.view.ViewTreeObserver;
 import android.view.View.OnAttachStateChangeListener;
 import android.view.View.OnKeyListener;
+import android.view.ViewTreeObserver;
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
 import android.widget.AbsListView;
 import android.widget.FrameLayout;
 import android.widget.HeaderViewListAdapter;
 import android.widget.ListAdapter;
-import android.widget.MenuItemHoverListener;
 import android.widget.ListView;
+import android.widget.MenuItemHoverListener;
 import android.widget.MenuPopupWindow;
 import android.widget.PopupWindow;
 import android.widget.PopupWindow.OnDismissListener;
@@ -40,6 +34,11 @@
 import com.android.internal.R;
 import com.android.internal.util.Preconditions;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * A popup for a menu which will allow multiple submenus to appear in a cascading fashion, side by
  * side.
@@ -70,7 +69,7 @@
     private final Handler mSubMenuHoverHandler;
 
     /** List of menus that were added before this popup was shown. */
-    private final List<MenuBuilder> mPendingMenus = new LinkedList<>();
+    private final List<MenuBuilder> mPendingMenus = new ArrayList<>();
 
     /**
      * List of open menus. The first item is the root menu and each
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index fb5b5ff..e13ac32 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -62,6 +62,7 @@
 
 namespace android {
 
+using gui::CaptureArgs;
 using gui::FocusRequest;
 
 static void doThrowNPE(JNIEnv* env) {
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index c33b7c9..e8f7b93 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -382,6 +382,7 @@
     optional bool in_size_compat_mode = 32;
     optional float min_aspect_ratio = 33;
     optional bool provides_max_bounds = 34;
+    optional bool enable_recents_screenshot = 35;
 }
 
 /* represents WindowToken */
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 58a3bb4..b39df2c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5970,7 +5970,7 @@
     <!-- @SystemApi Allows an application to turn on / off quiet mode.
          @hide -->
     <permission android:name="android.permission.MODIFY_QUIET_MODE"
-                android:protectionLevel="signature|privileged|development" />
+                android:protectionLevel="signature|privileged|development|role" />
 
     <!-- Allows internal management of the camera framework
          @hide -->
@@ -6923,6 +6923,10 @@
                  android:permission="android.permission.BIND_JOB_SERVICE">
         </service>
 
+        <service android:name="com.android.server.BinaryTransparencyService$UpdateMeasurementsJobService"
+                 android:permission="android.permission.BIND_JOB_SERVICE">
+        </service>
+
         <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
             android:exported="false">
             <intent-filter>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 181a26f..5c6eb69 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -545,7 +545,7 @@
     <string name="permdesc_bluetooth_advertise" product="default" msgid="6085174451034210183">"Laat die program toe om op Bluetooth-toestelle in die omtrek te adverteer"</string>
     <string name="permlab_uwb_ranging" msgid="8141915781475770665">"bepaal relatiewe posisie tussen ultrabreëbandtoestelle in die omtrek"</string>
     <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"Laat die program toe om relatiewe posisie tussen ultrabreëbandtoestelle in die omtrek te bepaal"</string>
-    <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"interaksie met wi‑fi-toestelle in die omtrek te hê"</string>
+    <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"om interaksie met wi‑fi-toestelle in die omtrek te hê"</string>
     <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Laat die program toe om op toestelle in die omtrek te adverteer, aan hulle te koppel en hul relatiewe posisie te bepaal"</string>
     <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"Voorkeur-NFC-betalingdiensinligting"</string>
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Laat die program toe om voorkeur-NFC-betalingdiensinligting soos geregistreerde hulpmiddels en roetebestemming te kry."</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 94a02a6..4c5cbbc 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -550,7 +550,7 @@
     <string name="permlab_uwb_ranging" msgid="8141915781475770665">"تحديد الموضع النسبي بين الأجهزة المجاورة التي تستخدم النطاق الواسع جدًا"</string>
     <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"يسمح هذا الإذن للتطبيق بتحديد الموضع النسبي بين الأجهزة المجاورة التي تستخدم النطاق الواسع جدًا."</string>
     <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"‏التفاعل مع أجهزة Wi‑Fi المجاورة"</string>
-    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"‏للسماح للتطبيق بالإعلان والربط وتحديد الموقع النسبي لأجهزة Wi-Fi المجاورة."</string>
+    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"‏للسماح للتطبيق بعرض الإعلانات والاتصال بالأجهزة الأخرى وتحديد الموقع النسبي لأجهزة Wi-Fi المجاورة."</string>
     <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"‏معلومات الخدمات المدفوعة باستخدام الاتصال قصير المدى NFC المفضّل"</string>
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"‏يسمح هذا الإذن للتطبيق بالحصول على معلومات الخدمات المدفوعة باستخدام الاتصال قصير المدى NFC المفضّل، مثلاً المساعدات المسجّلة ووجهة المسار."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"التحكم في اتصال الحقل القريب"</string>
@@ -1451,7 +1451,7 @@
     <string name="permdesc_requestDeletePackages" msgid="6133633516423860381">"للسماح لتطبيق ما بطلب حذف الحِزم."</string>
     <string name="permlab_requestIgnoreBatteryOptimizations" msgid="7646611326036631439">"طلب تجاهل تحسينات البطارية"</string>
     <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"للسماح للتطبيق بطلب الإذن لتجاهل تحسينات البطارية في هذا التطبيق."</string>
-    <string name="permlab_queryAllPackages" msgid="2928450604653281650">"طلب البحث في كل الحِزم"</string>
+    <string name="permlab_queryAllPackages" msgid="2928450604653281650">"البحث في كل الحِزم"</string>
     <string name="permdesc_queryAllPackages" msgid="5339069855520996010">"يسمح هذا الإذن للتطبيق بعرض كل الحِزم المثبّتة."</string>
     <string name="permlab_accessSupplementalApi" msgid="3544659160536960275">"‏الوصول إلى SupplementalApis"</string>
     <string name="permdesc_accessSupplementalApi" msgid="8974758769370951074">"‏السماح لأحد التطبيقات بالوصول إلى SupplementalApis"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index bb5c1022..3efa4b3 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -546,7 +546,7 @@
     <string name="permlab_uwb_ranging" msgid="8141915781475770665">"আশেপাশের Ultra-Wideband ডিভাইসগুলির আপেক্ষিক অবস্থান নির্ণয় করুন"</string>
     <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"অ্যাপকে আশেপাশের Ultra-Wideband ডিভাইসগুলির আপেক্ষিক অবস্থান নির্ণয় করার অনুমতি দিন"</string>
     <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"আশপাশের ওয়াই-ফাই ডিভাইসের সাথে ইন্টার‍্যাক্ট করুন"</string>
-    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"এটির ফলে অ্যাপ আশপাশের ওয়াই-ফাই ডিভাইসের তথ্য দেখাতে, তাদের সাথে কানেক্ট করতে এবং ত দূরত্বে আছে সেটি জানতে পারবে"</string>
+    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"এটির ফলে অ্যাপ আশপাশের ওয়াই-ফাই ডিভাইসের তথ্য দেখতে, তাদের সাথে কানেক্ট করতে এবং তা কত দূরত্বে আছে সেটি জানতে পারবে"</string>
     <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"পছন্দের NFC পেমেন্ট পরিষেবার তথ্য"</string>
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"অ্যাপের মাধ্যমে পছন্দসই এনএফসি পেমেন্ট পরিষেবার তথ্য, যেমন রেজিস্ট্রার করার সহায়তা এবং রুট ডেস্টিনেশন সম্পর্কিত তথ্য অ্যাক্সেস করার অনুমতি দেয়।"</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"নিয়ার ফিল্ড কমিউনিকেশন নিয়ন্ত্রণ করে"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index a07d941..4cc027e 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -1952,7 +1952,7 @@
     <string name="demo_restarting_message" msgid="1160053183701746766">"Palautetaan asetuksia…"</string>
     <string name="suspended_widget_accessibility" msgid="6331451091851326101">"<xliff:g id="LABEL">%1$s</xliff:g> ei ole käytössä."</string>
     <string name="conference_call" msgid="5731633152336490471">"Puhelinneuvottelu"</string>
-    <string name="tooltip_popup_title" msgid="7863719020269945722">"Työkaluvinkki"</string>
+    <string name="tooltip_popup_title" msgid="7863719020269945722">"Vihjeteksti"</string>
     <string name="app_category_game" msgid="4534216074910244790">"Pelit"</string>
     <string name="app_category_audio" msgid="8296029904794676222">"Musiikki ja ääni"</string>
     <string name="app_category_video" msgid="2590183854839565814">"Elokuvat ja videot"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 6e55c1d..284c757 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -546,7 +546,7 @@
     <string name="permlab_uwb_ranging" msgid="8141915781475770665">"déterminer position relative entre appareils ultra-wideband à proximité"</string>
     <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"Autoriser l\'appli à déterminer la position relative entre des appareils ultra-wideband à proximité"</string>
     <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"interagir avec les appareils Wi-Fi à proximité"</string>
-    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Permet à l\'appli de déterminer la position relative des appareils Wi‑Fi à proximité, vous en informer et s\'y connecter"</string>
+    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Permet à l\'appli de déterminer la position approximative des appareils Wi‑Fi à proximité, de les afficher et de s\'y connecter"</string>
     <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"Informations sur le service de paiement NFC préféré"</string>
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Permet à l\'application d\'obtenir des informations sur le service de paiement NFC préféré, y compris les ID d\'applications et les destinations de routage enregistrés."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"contrôler la communication en champ proche"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 76ca7a9..861d0bc 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -541,11 +541,11 @@
     <string name="permdesc_bluetooth_scan" product="default" msgid="6540723536925289276">"Թույլ է տալիս հավելվածին հայտնաբերել և զուգակցել մոտակա Bluetooth սարքերը"</string>
     <string name="permlab_bluetooth_connect" msgid="6657463246355003528">"միանալ զուգակցված Bluetooth սարքերի"</string>
     <string name="permdesc_bluetooth_connect" product="default" msgid="4546016548795544617">"Թույլ է տալիս հավելվածին միանալ զուգակցված Bluetooth սարքերի"</string>
-    <string name="permlab_bluetooth_advertise" msgid="2781147747928853177">"Գովազդ մոտակա Bluetooth սարքերում"</string>
+    <string name="permlab_bluetooth_advertise" msgid="2781147747928853177">"գովազդել մոտակա Bluetooth սարքերին"</string>
     <string name="permdesc_bluetooth_advertise" product="default" msgid="6085174451034210183">"Թույլատրում է հավելվածին գովազդ փոխանցել մոտակա Bluetooth սարքերին"</string>
     <string name="permlab_uwb_ranging" msgid="8141915781475770665">"որոշել մոտակա UWB սարքերի միջև հարաբերական դիրքավորումը"</string>
     <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"Թույլատրել հավելվածին որոշել գերլայնաշերտ կապի տեխնոլոգիան աջակցող մոտակա սարքերի միջև հարաբերական դիրքավորումը"</string>
-    <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"Փոխազդում մոտակա Wi‑Fi սարքերի հետ"</string>
+    <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"փոխներգործել մոտակա Wi‑Fi սարքերի հետ"</string>
     <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Թույլ է տալիս հավելվածին տվյալներ փոխանցել մոտակա Wi‑Fi սարքերին, միանալ դրանց և որոշել դրանց մոտավոր դիրքը։"</string>
     <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"Տեղեկություններ NFC վճարային ծառայության մասին"</string>
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Թույլ է տալիս հավելվածին ստանալ նախընտրելի NFC վճարային ծառայության մասին տեղեկություններ (օր․՝ գրանցված լրացուցիչ սարքերի և երթուղու նպատակակետի մասին տվյալներ)։"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 0a41b20..cc299bd 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -541,7 +541,7 @@
     <string name="permdesc_bluetooth_scan" product="default" msgid="6540723536925289276">"საშუალებას აძლევს აპს, აღმოაჩინოს ახლომახლო Bluetooth მოწყობილობები დასაწყვილებლად"</string>
     <string name="permlab_bluetooth_connect" msgid="6657463246355003528">"დაწყვილებულ Bluetooth მოწყობილობებთან დაკავშირება"</string>
     <string name="permdesc_bluetooth_connect" product="default" msgid="4546016548795544617">"საშუალებას აძლევს აპს, დაუკავშირდეს დაწყვილებულ Bluetooth მოწყობილობებს"</string>
-    <string name="permlab_bluetooth_advertise" msgid="2781147747928853177">"ახლ. Bluetooth მოწყობილობებზე რეკლამის განთავსება"</string>
+    <string name="permlab_bluetooth_advertise" msgid="2781147747928853177">"ახლ. Bluetooth მოწყობილობებზე მონაცემების მაუწყებლობა"</string>
     <string name="permdesc_bluetooth_advertise" product="default" msgid="6085174451034210183">"საშუალებას აძლევს აპს, რეკლამა განათავსოს ახლომახლო Bluetooth მოწყობილობებზე"</string>
     <string name="permlab_uwb_ranging" msgid="8141915781475770665">"შედარებითი პოზიციის დადგენა ახლომახლო ულტრაფართო სიხშირის მოწყობილობების შესახებ"</string>
     <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"ნებას რთავს აპს, დაადგინოს შედარებითი პოზიცია ახლომახლო ულტრაფართო სიხშირის მოწყობილობების შესახებ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 6b3311c..0b08c30 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -539,9 +539,9 @@
     <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Leidžiama programai peržiūrėti „Bluetooth“ konfigūraciją planšetiniame kompiuteryje ir užmegzti bei priimti ryšius iš susietų įrenginių."</string>
     <string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Programai leidžiama peržiūrėti „Bluetooth“ konfigūraciją „Android TV“ įrenginyje ir užmegzti bei priimti ryšius iš susietų įrenginių."</string>
     <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Leidžiama programai peržiūrėti „Bluetooth“ konfigūraciją telefone ir užmegzti bei priimti ryšius iš susietų įrenginių."</string>
-    <string name="permlab_bluetooth_scan" msgid="5402587142833124594">"„Bluetooth“ įr. netoliese aptikimas ir susiejimas"</string>
+    <string name="permlab_bluetooth_scan" msgid="5402587142833124594">"aptikti „Bluetooth“ įr. netoliese ir susieti"</string>
     <string name="permdesc_bluetooth_scan" product="default" msgid="6540723536925289276">"Leidžiama programai aptikti ir susieti „Bluetooth“ įrenginius netoliese"</string>
-    <string name="permlab_bluetooth_connect" msgid="6657463246355003528">"prisijungimas prie susietų „Bluetooth“ įrenginių"</string>
+    <string name="permlab_bluetooth_connect" msgid="6657463246355003528">"prisijungti prie susietų „Bluetooth“ įrenginių"</string>
     <string name="permdesc_bluetooth_connect" product="default" msgid="4546016548795544617">"Leidžiama programai prisijungti prie susietų „Bluetooth“ įrenginių"</string>
     <string name="permlab_bluetooth_advertise" msgid="2781147747928853177">"reklamuoti netoliese es. „Bluetooth“ įrenginiuose"</string>
     <string name="permdesc_bluetooth_advertise" product="default" msgid="6085174451034210183">"Programai leidžiama reklamuoti netoliese esančiuose „Bluetooth“ įrenginiuose"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 518b4e3..2b78d73 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -546,7 +546,7 @@
     <string name="permlab_uwb_ranging" msgid="8141915781475770665">"да ја одреди релативната положба помеѓу уредите со ултраширок појас во близина"</string>
     <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"Дозволува апликацијата да ја одреди релативната положба помеѓу уредите со ултраширок појас во близина"</string>
     <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"да има интеракција со уредите со Wi‑Fi во близина"</string>
-    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Дозволува апликацијата да рекламира, да се поврзува и да ја одредува релативната положба уредите со Wi‑Fi во близина"</string>
+    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Дозволува апликацијата да рекламира, да се поврзува и да ја одредува релативната положба на уреди со Wi‑Fi во близина"</string>
     <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"Информации за претпочитаната услуга за плаќање преку NFC"</string>
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Дозволува апликацијата да добие информации за претпочитаната услуга за плаќање преку NFC, како регистрирани помагала и дестинација на маршрутата."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"контролирај комуникација на блиско поле"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 543b8be..ff7919a 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -546,7 +546,7 @@
     <string name="permlab_uwb_ranging" msgid="8141915781475770665">"tentukan kedudukan relatif antara peranti Ultrajalur Lebar berdekatan"</string>
     <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"Benarkan apl menentukan kedudukan relatif antara peranti Ultrajalur Lebar berdekatan"</string>
     <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"berinteraksi dengan peranti Wi-Fi berdekatan"</string>
-    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Membenarkan apl mengiklankan, menyambung dan menentukan penempatan relatif peranti Wi-Fi berdekatan"</string>
+    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Membenarkan apl mengiklankan, menyambung dan menentukan kedudukan relatif peranti Wi-Fi berdekatan"</string>
     <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"Maklumat Perkhidmatan Pembayaran NFC Pilihan"</string>
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Membenarkan apl mendapatkan maklumat perkhidmatan pembayaran nfc pilihan seperti bantuan berdaftar dan destinasi laluan."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"mengawal Komunikasi Medan Dekat"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index ea11ae9..75f38c0 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -541,12 +541,12 @@
     <string name="permdesc_bluetooth_scan" product="default" msgid="6540723536925289276">"Hiermee kan de app bluetooth-apparaten in de buurt vinden en koppelen"</string>
     <string name="permlab_bluetooth_connect" msgid="6657463246355003528">"verbinding maken met gekoppelde bluetooth-apparaten"</string>
     <string name="permdesc_bluetooth_connect" product="default" msgid="4546016548795544617">"Hiermee kan de app verbinding maken met gekoppelde bluetooth-apparaten"</string>
-    <string name="permlab_bluetooth_advertise" msgid="2781147747928853177">"adverteren op bluetooth-apparaten in de buurt"</string>
-    <string name="permdesc_bluetooth_advertise" product="default" msgid="6085174451034210183">"Hiermee kan de app adverteren op bluetooth-apparaten in de buurt"</string>
+    <string name="permlab_bluetooth_advertise" msgid="2781147747928853177">"uitzenden naar bluetooth-apparaten in de buurt"</string>
+    <string name="permdesc_bluetooth_advertise" product="default" msgid="6085174451034210183">"Hiermee kan de app uitzenden naar bluetooth-apparaten in de buurt"</string>
     <string name="permlab_uwb_ranging" msgid="8141915781475770665">"relatieve positie tussen ultrabreedbandapparaten in de buurt bepalen"</string>
     <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"De app toestaan om de relatieve positie tussen ultrabreedbandapparaten in de buurt te bepalen"</string>
     <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"interactie met wifi-apparaten in de buurt"</string>
-    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Hiermee kan de app adverteren op, verbinding maken met en de relatieve positie bepalen van wifi-apparaten in de buurt"</string>
+    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Hiermee kan de app uitzenden, verbindingen maken en de relatieve positie bepalen van wifi-apparaten in de buurt"</string>
     <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"Informatie over voorkeursservice voor NFC-betaling"</string>
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Hiermee kun je zorgen dat de app informatie krijgt over de voorkeursservice voor NFC-betaling, zoals geregistreerde hulpmiddelen en routebestemmingen."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"Near Field Communication regelen"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index d9c7e3a..c70bebc 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -542,12 +542,12 @@
     <string name="permdesc_bluetooth_scan" product="default" msgid="6540723536925289276">"Permite aplicației să descopere și să asocieze dispozitive Bluetooth din apropiere"</string>
     <string name="permlab_bluetooth_connect" msgid="6657463246355003528">"să se conecteze la dispozitive Bluetooth asociate"</string>
     <string name="permdesc_bluetooth_connect" product="default" msgid="4546016548795544617">"Permite aplicației să se conecteze la dispozitive Bluetooth asociate"</string>
-    <string name="permlab_bluetooth_advertise" msgid="2781147747928853177">"să difuzeze anunțuri pe dispozitive Bluetooth din apropiere"</string>
+    <string name="permlab_bluetooth_advertise" msgid="2781147747928853177">"să transmită anunțuri pe dispozitive Bluetooth din apropiere"</string>
     <string name="permdesc_bluetooth_advertise" product="default" msgid="6085174451034210183">"Permite aplicației să difuzeze anunțuri pe dispozitive Bluetooth din apropiere"</string>
     <string name="permlab_uwb_ranging" msgid="8141915781475770665">"să stabilească poziția relativă dintre dispozitivele Ultra-Wideband din apropiere"</string>
     <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"Permiteți-i aplicației să stabilească poziția relativă dintre dispozitivele Ultra-Wideband din apropiere"</string>
     <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"să interacționeze cu dispozitive Wi‑Fi din apropiere"</string>
-    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Permite aplicației să se conecteze la, să afișeze date și să stabilească poziția relativă pentru dispozitive Wi-Fi din apropiere"</string>
+    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Permite aplicației să se conecteze la dispozitive Wi-Fi din apropiere, să transmită anunțuri și să stabilească poziția relativă a acestora"</string>
     <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"Informații despre serviciul de plăți NFC preferat"</string>
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Permite aplicației să obțină informații despre serviciul de plăți NFC preferat, de exemplu, identificatorii de aplicație înregistrați și destinația traseului."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"controlare schimb de date prin Near Field Communication"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index b84a85f..8d2c534 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -537,13 +537,13 @@
     <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Huruhusu programu kuona usanidi wa Bluetooth kwenye kompyuta kibao, na kutuma na kukubali miunganisho kwa vifaa vilivyooanishwa."</string>
     <string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Huruhusu programu iangalie mipangilio iliyowekwa ya Bluetooth kwenye kifaa chako cha Android TV na kufanya na kukubali miunganisho na vifaa vilivyooanishwa."</string>
     <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Huruhusu programu kuona usanidi wa Bluetooth kwenye simu, na kutuma na kukubali miunganisho kwa vifaa vilivyooanishwa."</string>
-    <string name="permlab_bluetooth_scan" msgid="5402587142833124594">"kutambua na kuoanisha vifaa vyenye Bluetooth vilivyo karibu"</string>
+    <string name="permlab_bluetooth_scan" msgid="5402587142833124594">"tambua na oanishe vifaa vyenye Bluetooth vilivyo karibu"</string>
     <string name="permdesc_bluetooth_scan" product="default" msgid="6540723536925289276">"Huruhusu programu itambue na kuoanisha kwenye vifaa vyenye Bluetooth vilivyo karibu"</string>
-    <string name="permlab_bluetooth_connect" msgid="6657463246355003528">"kuunganisha kwenye vifaa vyenye Bluetooth vilivyooanishwa"</string>
+    <string name="permlab_bluetooth_connect" msgid="6657463246355003528">"unganisha kwenye vifaa vyenye Bluetooth vilivyooanishwa"</string>
     <string name="permdesc_bluetooth_connect" product="default" msgid="4546016548795544617">"Huruhusu programu iunganishe kwenye vifaa vyenye Bluetooth vilivyooanishwa"</string>
-    <string name="permlab_bluetooth_advertise" msgid="2781147747928853177">"kutangaza kwenye vifaa vyenye Bluetooth vilivyo karibu"</string>
+    <string name="permlab_bluetooth_advertise" msgid="2781147747928853177">"tangaza kwenye vifaa vyenye Bluetooth vilivyo karibu"</string>
     <string name="permdesc_bluetooth_advertise" product="default" msgid="6085174451034210183">"Huruhusu programu itangaze kwenye vifaa vyenye Bluetooth vilivyo karibu"</string>
-    <string name="permlab_uwb_ranging" msgid="8141915781475770665">"kubainisha nafasi kati ya vifaa vyenye Bendi Pana Zaidi vilivyo karibu"</string>
+    <string name="permlab_uwb_ranging" msgid="8141915781475770665">"bainisha nafasi kati ya vifaa vyenye Bendi Pana Zaidi vilivyo karibu"</string>
     <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"Ruhusu programu ibainishe nafasi kati ya vifaa vyenye Bendi Pana Zaidi vilivyo karibu"</string>
     <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"tumia vifaa vya Wi‑Fi vilivyo karibu"</string>
     <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Huruhusu programu kutangaza, kuunganisha na kubaini mahali palipokadiriwa vilipo vifaa vya Wi-Fi vilivyo karibu"</string>
diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml
index 0db08fb..88bf18c 100644
--- a/core/res/res/values-television/config.xml
+++ b/core/res/res/values-television/config.xml
@@ -24,19 +24,6 @@
     <!-- Flags enabling default window features. See Window.java -->
     <bool name="config_defaultWindowFeatureOptionsPanel">false</bool>
 
-    <!-- The percentage of the screen width to use for the default width or height of
-         picture-in-picture windows. Regardless of the percent set here, calculated size will never
-         be smaller than @dimen/default_minimal_size_pip_resizable_task. -->
-    <item name="config_pictureInPictureDefaultSizePercent" format="float" type="dimen">0.2</item>
-
-    <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows.
-         These values are in DPs and will be converted to pixel sizes internally. -->
-    <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">24x24</string>
-
-    <!-- The default gravity for the picture-in-picture window.
-         Currently, this maps to Gravity.BOTTOM | Gravity.RIGHT -->
-    <integer name="config_defaultPictureInPictureGravity">0x55</integer>
-
     <!-- The maximum height of the expanded horizontal picture-in-picture window -->
     <item name="config_pictureInPictureExpandedHorizontalHeight"
           format="dimension" type="dimen">110dp</item>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index e455689..2044e49 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -546,7 +546,7 @@
     <string name="permlab_uwb_ranging" msgid="8141915781475770665">"tukuyin ang relatibong posisyon sa pagitan ng mga kalapit na Ultra-Wideband device"</string>
     <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"Payagan ang app na tukuyin ang relatibong posisyon sa pagitan ng mga kalapit na Ultra-Wideband device"</string>
     <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"makipag-ugnayan sa mga kalapit na Wi‑Fi device"</string>
-    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Nagbibigay-daan sa app na i-advertise, kumonekta sa, at tukuyin ang nauugnay na posisyon ng mga kalapit na Wi‑Fi device"</string>
+    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Nagbibigay-daan sa app na i-advertise ang, kumonekta sa, at tukuyin ang nauugnay na posisyon ng mga kalapit na Wi‑Fi device"</string>
     <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"Impormasyon sa Gustong NFC na Serbisyo sa Pagbabayad"</string>
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Pinapayagan ang app na makakuha ng impormasyon sa gustong nfc na serbisyo sa pagbabayad tulad ng mga nakarehistrong application ID at destinasyon ng ruta."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"kontrolin ang Near Field Communication"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 07a98c9..cf1b14c 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -546,7 +546,7 @@
     <string name="permlab_uwb_ranging" msgid="8141915781475770665">"xác định khoảng cách tương đối giữa các thiết bị ở gần dùng Băng tần siêu rộng"</string>
     <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"Cho phép ứng dụng xác định khoảng cách tương đối giữa các thiết bị ở gần dùng Băng tần siêu rộng"</string>
     <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"tương tác với các thiết bị Wi‑Fi lân cận"</string>
-    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Cho phép ứng dụng thông báo, kết nối và xác định vị trí tương đối của các thiết bị Wi‑Fi lân cận"</string>
+    <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Cho phép ứng dụng này thông báo, kết nối và xác định vị trí tương đối của các thiết bị Wi‑Fi lân cận"</string>
     <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"Thông tin về dịch vụ thanh toán qua công nghệ giao tiếp tầm gần (NFC) được ưu tiên"</string>
     <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Cho phép ứng dụng nhận thông tin về dịch vụ thanh toán qua công nghệ giao tiếp tầm gần mà bạn ưu tiên, chẳng hạn như các hình thức hỗ trợ đã đăng ký và điểm đến trong hành trình."</string>
     <string name="permlab_nfc" msgid="1904455246837674977">"kiểm soát Liên lạc trường gần"</string>
diff --git a/core/res/res/values/colors_car.xml b/core/res/res/values/colors_car.xml
index 82caa26..d7d222c 100644
--- a/core/res/res/values/colors_car.xml
+++ b/core/res/res/values/colors_car.xml
@@ -133,8 +133,8 @@
     <!-- The color of the seekbar track background in SeekbarListItem. This color is assumed to be
          on a light-colored background. -->
     <color name="car_seekbar_track_background">@color/car_seekbar_track_background_dark</color>
-    <!-- background is car_grey_868 with .9 alpha -->
-    <color name="car_toast_background">#E6282a2d</color>
+    <!-- background is car_grey_868 with -->
+    <color name="car_toast_background">@color/car_grey_868</color>
 
     <!-- Misc colors -->
     <color name="car_highlight_light">#ff66b5ff</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 05894d5..5ac30de 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3704,26 +3704,6 @@
          snapped to any position between the first target and the last target. -->
     <bool name="config_dockedStackDividerFreeSnapMode">false</bool>
 
-    <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows.
-         These values are in DPs and will be converted to pixel sizes internally. -->
-    <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">16x16</string>
-
-    <!-- The percentage of the screen width to use for the default width or height of
-         picture-in-picture windows. Regardless of the percent set here, calculated size will never
-         be smaller than @dimen/default_minimal_size_pip_resizable_task. -->
-    <item name="config_pictureInPictureDefaultSizePercent" format="float" type="dimen">0.23</item>
-
-    <!-- The default aspect ratio for picture-in-picture windows. -->
-    <item name="config_pictureInPictureDefaultAspectRatio" format="float" type="dimen">1.777778</item>
-
-    <!-- This is the limit for the max and min aspect ratio (1 / this value) at which the min size
-         will be used instead of an adaptive size based loosely on area. -->
-    <item name="config_pictureInPictureAspectRatioLimitForMinSize" format="float" type="dimen">1.777778</item>
-
-    <!-- The default gravity for the picture-in-picture window.
-         Currently, this maps to Gravity.BOTTOM | Gravity.RIGHT -->
-    <integer name="config_defaultPictureInPictureGravity">0x55</integer>
-
     <!-- The minimum aspect ratio (width/height) that is supported for picture-in-picture.  Any
          ratio smaller than this is considered too tall and thin to be usable. Currently, this
          is the inverse of the max landscape aspect ratio (1:2.39), but this is an extremely
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index adf8f8e..032d0b9 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -717,16 +717,6 @@
     <!-- The default minimal size of a resizable task, in both dimensions. -->
     <dimen name="default_minimal_size_resizable_task">220dp</dimen>
 
-    <!-- The default minimal size of a PiP task, in both dimensions. -->
-    <dimen name="default_minimal_size_pip_resizable_task">108dp</dimen>
-
-    <!--
-      The overridable minimal size of a PiP task, in both dimensions.
-      Different from default_minimal_size_pip_resizable_task, this is to limit the dimension
-      when the pinned stack size is overridden by app via minWidth/minHeight.
-    -->
-    <dimen name="overridable_minimal_size_pip_resizable_task">48dp</dimen>
-
     <!-- Height of a task when in minimized mode from the top when launcher is resizable. -->
     <dimen name="task_height_of_minimized_mode">80dp</dimen>
 
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index bccd2b6..d80645e 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -281,8 +281,8 @@
   <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_SWIPE_DOWN}. -->
   <item type="id" name="accessibilityActionSwipeDown" />
 
-  <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_SHOW_SUGGESTIONS}. -->
-  <item type="id" name="accessibilityActionShowSuggestions" />
+  <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_SHOW_TEXT_SUGGESTIONS}. -->
+  <item type="id" name="accessibilityActionShowTextSuggestions" />
 
   <!-- View tag for remote views to store the index of the next child when adding nested remote views dynamically. -->
   <item type="id" name="remote_views_next_child" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 3beb4b2..46a77f9 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3288,7 +3288,7 @@
     <public name="accessibilityActionSwipeRight" />
     <public name="accessibilityActionSwipeUp" />
     <public name="accessibilityActionSwipeDown" />
-    <public name="accessibilityActionShowSuggestions" />
+    <public name="accessibilityActionShowTextSuggestions" />
     <public name="inputExtractAction" />
     <public name="inputExtractAccessories" />
   </staging-public-group>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 558e3c3..60a3398 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -408,11 +408,6 @@
   <java-symbol type="array" name="config_localPrivateDisplayPorts" />
   <java-symbol type="integer" name="config_defaultDisplayDefaultColorMode" />
   <java-symbol type="bool" name="config_enableAppWidgetService" />
-  <java-symbol type="string" name="config_defaultPictureInPictureScreenEdgeInsets" />
-  <java-symbol type="dimen" name="config_pictureInPictureDefaultSizePercent" />
-  <java-symbol type="dimen" name="config_pictureInPictureDefaultAspectRatio" />
-  <java-symbol type="dimen" name="config_pictureInPictureAspectRatioLimitForMinSize" />
-  <java-symbol type="integer" name="config_defaultPictureInPictureGravity" />
   <java-symbol type="dimen" name="config_pictureInPictureMinAspectRatio" />
   <java-symbol type="dimen" name="config_pictureInPictureMaxAspectRatio" />
   <java-symbol type="integer" name="config_pictureInPictureMaxNumberOfActions" />
@@ -1295,6 +1290,7 @@
   <java-symbol type="array" name="vendor_required_apps_managed_user" />
   <java-symbol type="array" name="vendor_required_apps_managed_profile" />
   <java-symbol type="array" name="vendor_required_apps_managed_device" />
+  <java-symbol type="array" name="vendor_required_attestation_certificates" />
   <java-symbol type="array" name="vendor_disallowed_apps_managed_user" />
   <java-symbol type="array" name="vendor_disallowed_apps_managed_profile" />
   <java-symbol type="array" name="vendor_disallowed_apps_managed_device" />
@@ -2016,8 +2012,6 @@
   <java-symbol type="id" name="replace_message" />
   <java-symbol type="fraction" name="config_dimBehindFadeDuration" />
   <java-symbol type="dimen" name="default_minimal_size_resizable_task" />
-  <java-symbol type="dimen" name="default_minimal_size_pip_resizable_task" />
-  <java-symbol type="dimen" name="overridable_minimal_size_pip_resizable_task" />
   <java-symbol type="dimen" name="task_height_of_minimized_mode" />
   <java-symbol type="fraction" name="config_screenAutoBrightnessDozeScaleFactor" />
   <java-symbol type="bool" name="config_allowPriorityVibrationsInLowPowerMode" />
diff --git a/core/res/res/values/vendor_required_attestation_certificates.xml b/core/res/res/values/vendor_required_attestation_certificates.xml
new file mode 100644
index 0000000..ce5660f
--- /dev/null
+++ b/core/res/res/values/vendor_required_attestation_certificates.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources>
+  <!-- The PEM-encoded certificates added here are used for verifying attestations.
+    The trustworthiness of the attestation depends on the root certificate of the chain.
+
+    Certificates that can be used can be retrieved from:
+    https://developer.android.com/training/articles/security-key-attestation#root_certificate.
+
+    If not already present in resource overlay, please add
+    vendor_required_attestation_certificates.xml (matching this file) in vendor overlay
+    with <item></item> of the PEM-encoded root certificates.
+  -->
+    <string-array translatable="false" name="vendor_required_attestation_certificates">
+    </string-array>
+</resources>
diff --git a/core/tests/coretests/src/android/content/ManagedUserContentResolverTest.java b/core/tests/coretests/src/android/content/ManagedUserContentResolverTest.java
index 68d4cd4..1bc46a7 100644
--- a/core/tests/coretests/src/android/content/ManagedUserContentResolverTest.java
+++ b/core/tests/coretests/src/android/content/ManagedUserContentResolverTest.java
@@ -20,7 +20,6 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.LargeTest;
 
@@ -37,7 +36,6 @@
  * Run: adb shell am instrument -e class android.content.ManagedUserContentResolverTest -w \
  *     com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
  */
-@Presubmit
 @LargeTest
 public class ManagedUserContentResolverTest extends AbstractCrossUserContentResolverTest {
     @Override
diff --git a/core/tests/coretests/src/android/content/SecondaryUserContentResolverTest.java b/core/tests/coretests/src/android/content/SecondaryUserContentResolverTest.java
index de4c572..dbe0278 100644
--- a/core/tests/coretests/src/android/content/SecondaryUserContentResolverTest.java
+++ b/core/tests/coretests/src/android/content/SecondaryUserContentResolverTest.java
@@ -18,7 +18,6 @@
 
 import android.content.pm.UserInfo;
 import android.os.RemoteException;
-import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.LargeTest;
 
@@ -35,7 +34,6 @@
  * Run: adb shell am instrument -e class android.content.SecondaryUserContentResolverTest -w \
  *     com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
  */
-@Presubmit
 @LargeTest
 public class SecondaryUserContentResolverTest extends AbstractCrossUserContentResolverTest {
     @Override
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 e79f1e3..dbe1e81 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -17,6 +17,8 @@
 package com.android.internal.os;
 
 import static android.os.BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
+import static android.os.BatteryStats.POWER_DATA_UNAVAILABLE;
+import static android.os.BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT;
 import static android.os.BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR;
 import static android.os.BatteryStats.STATS_SINCE_CHARGED;
 import static android.os.BatteryStats.WAKE_TYPE_PARTIAL;
@@ -26,7 +28,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.mock;
+
 import android.app.ActivityManager;
+import android.app.usage.NetworkStatsManager;
 import android.os.BatteryStats;
 import android.os.BatteryStats.HistoryItem;
 import android.os.BatteryStats.Uid.Sensor;
@@ -36,6 +41,7 @@
 import android.telephony.Annotation;
 import android.telephony.CellSignalStrength;
 import android.telephony.DataConnectionRealTimeInfo;
+import android.telephony.ModemActivityInfo;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.util.SparseIntArray;
@@ -50,6 +56,8 @@
 
 import junit.framework.TestCase;
 
+import org.mockito.Mock;
+
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
@@ -74,6 +82,13 @@
     private static final int ISOLATED_UID = UserHandle.getUid(0, ISOLATED_APP_ID);
     private static final WorkSource WS = new WorkSource(UID);
 
+    enum ModemState {
+        SLEEP, IDLE, RECEIVING, TRANSMITTING
+    }
+
+    @Mock
+    NetworkStatsManager mNetworkStatsManager;
+
     /**
      * Test BatteryStatsImpl.Uid.noteBluetoothScanResultLocked.
      */
@@ -1285,69 +1300,29 @@
     }
 
     @SmallTest
-    public void testGetPerStateActiveRadioDurationMs() {
+    public void testGetPerStateActiveRadioDurationMs_noModemActivity() {
         final MockClock clock = new MockClock(); // holds realtime and uptime in ms
         final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clock);
-        final int ratCount = BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT;
+        final int ratCount = RADIO_ACCESS_TECHNOLOGY_COUNT;
         final int frequencyCount = ServiceState.FREQUENCY_RANGE_MMWAVE + 1;
         final int txLevelCount = CellSignalStrength.getNumSignalStrengthLevels();
 
         final long[][][] expectedDurationsMs = new long[ratCount][frequencyCount][txLevelCount];
+        final long[][] expectedRxDurationsMs = new long[ratCount][frequencyCount];
+        final long[][][] expectedTxDurationsMs = new long[ratCount][frequencyCount][txLevelCount];
         for (int rat = 0; rat < ratCount; rat++) {
             for (int freq = 0; freq < frequencyCount; freq++) {
+                // Should have no RX data without Modem Activity Info
+                expectedRxDurationsMs[rat][freq] = POWER_DATA_UNAVAILABLE;
                 for (int txLvl = 0; txLvl < txLevelCount; txLvl++) {
                     expectedDurationsMs[rat][freq][txLvl] = 0;
+                    // Should have no TX data without Modem Activity Info
+                    expectedTxDurationsMs[rat][freq][txLvl] = POWER_DATA_UNAVAILABLE;
                 }
             }
         }
 
-        class ModemAndBatteryState {
-            public long currentTimeMs = 100;
-            public boolean onBattery = false;
-            public boolean modemActive = false;
-            @Annotation.NetworkType
-            public int currentNetworkDataType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
-            @BatteryStats.RadioAccessTechnology
-            public int currentRat = BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER;
-            @ServiceState.FrequencyRange
-            public int currentFrequencyRange = ServiceState.FREQUENCY_RANGE_UNKNOWN;
-            public SparseIntArray currentSignalStrengths = new SparseIntArray();
-
-            void setOnBattery(boolean onBattery) {
-                this.onBattery = onBattery;
-                bi.updateTimeBasesLocked(onBattery, Display.STATE_OFF, currentTimeMs * 1000,
-                        currentTimeMs * 1000);
-            }
-
-            void setModemActive(boolean active) {
-                modemActive = active;
-                final int state = active ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
-                        : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
-                bi.noteMobileRadioPowerStateLocked(state, currentTimeMs * 1000_000L, UID);
-            }
-
-            void setRatType(@Annotation.NetworkType int dataType,
-                    @BatteryStats.RadioAccessTechnology int rat) {
-                currentNetworkDataType = dataType;
-                currentRat = rat;
-                bi.notePhoneDataConnectionStateLocked(dataType, true, ServiceState.STATE_IN_SERVICE,
-                        currentFrequencyRange);
-            }
-
-            void setFrequencyRange(@ServiceState.FrequencyRange int frequency) {
-                currentFrequencyRange = frequency;
-                bi.notePhoneDataConnectionStateLocked(currentNetworkDataType, true,
-                        ServiceState.STATE_IN_SERVICE, frequency);
-            }
-
-            void setSignalStrength(@BatteryStats.RadioAccessTechnology int rat, int strength) {
-                currentSignalStrengths.put(rat, strength);
-                final int size = currentSignalStrengths.size();
-                final int newestGenSignalStrength = currentSignalStrengths.valueAt(size - 1);
-                bi.notePhoneSignalStrengthLocked(newestGenSignalStrength, currentSignalStrengths);
-            }
-        }
-        final ModemAndBatteryState state = new ModemAndBatteryState();
+        final ModemAndBatteryState state = new ModemAndBatteryState(bi, null);
 
         IntConsumer incrementTime = inc -> {
             state.currentTimeMs += inc;
@@ -1365,6 +1340,7 @@
             expectedDurationsMs[currentRat][currentFrequencyRange][currentSignalStrength] += inc;
         };
 
+
         state.setOnBattery(false);
         state.setModemActive(false);
         state.setRatType(TelephonyManager.NETWORK_TYPE_UNKNOWN,
@@ -1372,95 +1348,367 @@
         state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_UNKNOWN);
         state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER,
                 CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
-        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
 
         // While not on battery, the timers should not increase.
         state.setModemActive(true);
         incrementTime.accept(100);
-        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
 
         state.setRatType(TelephonyManager.NETWORK_TYPE_NR, BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR);
         incrementTime.accept(200);
-        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
 
         state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR,
                 CellSignalStrength.SIGNAL_STRENGTH_GOOD);
         incrementTime.accept(500);
-        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
 
         state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_MMWAVE);
         incrementTime.accept(300);
-        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
 
         state.setRatType(TelephonyManager.NETWORK_TYPE_LTE,
                 BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE);
         incrementTime.accept(400);
-        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
 
         state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
                 CellSignalStrength.SIGNAL_STRENGTH_MODERATE);
         incrementTime.accept(500);
-        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
 
         // When set on battery, currently active state (RAT:LTE, Signal Strength:Moderate) should
         // start counting up.
         state.setOnBattery(true);
         incrementTime.accept(600);
-        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
-
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
         // Changing LTE signal strength should be tracked.
         state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
                 CellSignalStrength.SIGNAL_STRENGTH_POOR);
         incrementTime.accept(700);
-        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
 
         state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
                 CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
         incrementTime.accept(800);
-        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
 
         state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
                 CellSignalStrength.SIGNAL_STRENGTH_GOOD);
         incrementTime.accept(900);
-        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
 
         state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
                 CellSignalStrength.SIGNAL_STRENGTH_GREAT);
         incrementTime.accept(1000);
-        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
 
         // Change in the signal strength of nonactive RAT should not affect anything.
         state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER,
                 CellSignalStrength.SIGNAL_STRENGTH_POOR);
         incrementTime.accept(1100);
-        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
 
         // Changing to OTHER Rat should start tracking the poor signal strength.
         state.setRatType(TelephonyManager.NETWORK_TYPE_CDMA,
                 BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER);
         incrementTime.accept(1200);
-        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
 
         // Noting frequency change should not affect non NR Rat.
         state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_HIGH);
         incrementTime.accept(1300);
-        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
 
         // Now the NR Rat, HIGH frequency range, good signal strength should start counting.
         state.setRatType(TelephonyManager.NETWORK_TYPE_NR, BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR);
         incrementTime.accept(1400);
-        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
 
         // Noting frequency change should not affect non NR Rat.
         state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_LOW);
         incrementTime.accept(1500);
-        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
 
         // Modem no longer active, should not be tracking any more.
         state.setModemActive(false);
         incrementTime.accept(1500);
-        checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+    }
 
+    @SmallTest
+    public void testGetPerStateActiveRadioDurationMs_withModemActivity() {
+        final MockClock clock = new MockClock(); // holds realtime and uptime in ms
+        final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clock);
+        bi.setPowerProfile(mock(PowerProfile.class));
+        final int ratCount = RADIO_ACCESS_TECHNOLOGY_COUNT;
+        final int frequencyCount = ServiceState.FREQUENCY_RANGE_MMWAVE + 1;
+        final int txLevelCount = CellSignalStrength.getNumSignalStrengthLevels();
+
+        final long[][][] expectedDurationsMs = new long[ratCount][frequencyCount][txLevelCount];
+        final long[][] expectedRxDurationsMs = new long[ratCount][frequencyCount];
+        final long[][][] expectedTxDurationsMs = new long[ratCount][frequencyCount][txLevelCount];
+        for (int rat = 0; rat < ratCount; rat++) {
+            for (int freq = 0; freq < frequencyCount; freq++) {
+                if (rat != RADIO_ACCESS_TECHNOLOGY_NR
+                        && freq != ServiceState.FREQUENCY_RANGE_UNKNOWN) {
+                    // Only the NR RAT should have per frequency data.
+                    expectedRxDurationsMs[rat][freq] = POWER_DATA_UNAVAILABLE;
+                } else {
+                    expectedRxDurationsMs[rat][freq] = 0;
+                }
+                expectedRxDurationsMs[rat][freq] = POWER_DATA_UNAVAILABLE;
+
+                for (int txLvl = 0; txLvl < txLevelCount; txLvl++) {
+                    expectedDurationsMs[rat][freq][txLvl] = 0;
+
+                    if (rat != RADIO_ACCESS_TECHNOLOGY_NR
+                            && freq != ServiceState.FREQUENCY_RANGE_UNKNOWN) {
+                        // Only the NR RAT should have per frequency data.
+                        expectedTxDurationsMs[rat][freq][txLvl] = POWER_DATA_UNAVAILABLE;
+                    } else {
+                        expectedTxDurationsMs[rat][freq][txLvl] = 0;
+                    }
+                    expectedTxDurationsMs[rat][freq][txLvl] = POWER_DATA_UNAVAILABLE;
+                }
+            }
+        }
+
+        final ModemActivityInfo mai = new ModemActivityInfo(0L, 0L, 0L, new int[txLevelCount], 0L);
+        final ModemAndBatteryState state = new ModemAndBatteryState(bi, mai);
+
+        IntConsumer incrementTime = inc -> {
+            state.currentTimeMs += inc;
+            clock.realtime = clock.uptime = state.currentTimeMs;
+
+            // If the device is not on battery, no timers should increment.
+            if (!state.onBattery) return;
+            // If the modem is not active, no timers should increment.
+            if (!state.modemActive) return;
+
+            final int currRat = state.currentRat;
+            final int currFreqRange =
+                    currRat == RADIO_ACCESS_TECHNOLOGY_NR ? state.currentFrequencyRange : 0;
+            int currSignalStrength = state.currentSignalStrengths.get(currRat);
+
+            expectedDurationsMs[currRat][currFreqRange][currSignalStrength] += inc;
+
+            // Evaluate the HAL provided time in states.
+            switch (state.modemState) {
+                case SLEEP:
+                    long sleepMs = state.modemActivityInfo.getSleepTimeMillis();
+                    state.modemActivityInfo.setSleepTimeMillis(sleepMs + inc);
+                    break;
+                case IDLE:
+                    long idleMs = state.modemActivityInfo.getIdleTimeMillis();
+                    state.modemActivityInfo.setIdleTimeMillis(idleMs + inc);
+                    break;
+                case RECEIVING:
+                    long rxMs = state.modemActivityInfo.getReceiveTimeMillis();
+                    state.modemActivityInfo.setReceiveTimeMillis(rxMs + inc);
+                    expectedRxDurationsMs[currRat][currFreqRange] += inc;
+                    break;
+                case TRANSMITTING:
+                    int[] txMs = state.modemActivityInfo.getTransmitTimeMillis();
+                    txMs[currSignalStrength] += inc;
+                    state.modemActivityInfo.setTransmitTimeMillis(txMs);
+                    expectedTxDurationsMs[currRat][currFreqRange][currSignalStrength] += inc;
+                    break;
+            }
+        };
+
+        state.setOnBattery(false);
+        state.setModemActive(false);
+        state.setRatType(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER);
+        state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_UNKNOWN);
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER,
+                CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        // While not on battery, the timers should not increase.
+        state.setModemActive(true);
+        incrementTime.accept(100);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        state.setRatType(TelephonyManager.NETWORK_TYPE_NR, BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR);
+        incrementTime.accept(200);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR,
+                CellSignalStrength.SIGNAL_STRENGTH_GOOD);
+        incrementTime.accept(500);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_MMWAVE);
+        incrementTime.accept(300);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        state.setRatType(TelephonyManager.NETWORK_TYPE_LTE,
+                BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE);
+        incrementTime.accept(400);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+                CellSignalStrength.SIGNAL_STRENGTH_MODERATE);
+        incrementTime.accept(500);
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        // Data will now be available.
+        for (int rat = 0; rat < ratCount; rat++) {
+            for (int freq = 0; freq < frequencyCount; freq++) {
+                if (rat == RADIO_ACCESS_TECHNOLOGY_NR
+                        || freq == ServiceState.FREQUENCY_RANGE_UNKNOWN) {
+                    // Only the NR RAT should have per frequency data.
+                    expectedRxDurationsMs[rat][freq] = 0;
+                }
+                for (int txLvl = 0; txLvl < txLevelCount; txLvl++) {
+                    if (rat == RADIO_ACCESS_TECHNOLOGY_NR
+                            || freq == ServiceState.FREQUENCY_RANGE_UNKNOWN) {
+                        // Only the NR RAT should have per frequency data.
+                        expectedTxDurationsMs[rat][freq][txLvl] = 0;
+                    }
+                }
+            }
+        }
+
+        // When set on battery, currently active state (RAT:LTE, Signal Strength:Moderate) should
+        // start counting up.
+        state.setOnBattery(true);
+        incrementTime.accept(300);
+        state.setModemState(ModemState.RECEIVING);
+        incrementTime.accept(500);
+        state.setModemState(ModemState.TRANSMITTING);
+        incrementTime.accept(600);
+        state.noteModemControllerActivity();
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+        // Changing LTE signal strength should be tracked.
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+                CellSignalStrength.SIGNAL_STRENGTH_POOR);
+        incrementTime.accept(300);
+        state.setModemState(ModemState.SLEEP);
+        incrementTime.accept(1000);
+        state.setModemState(ModemState.RECEIVING);
+        incrementTime.accept(700);
+        state.noteModemControllerActivity();
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+                CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        incrementTime.accept(800);
+        state.setModemState(ModemState.TRANSMITTING);
+        incrementTime.accept(222);
+        state.setModemState(ModemState.IDLE);
+        incrementTime.accept(111);
+        state.setModemState(ModemState.RECEIVING);
+        incrementTime.accept(7777);
+        state.noteModemControllerActivity();
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+                CellSignalStrength.SIGNAL_STRENGTH_GOOD);
+        incrementTime.accept(88);
+        state.setModemState(ModemState.TRANSMITTING);
+        incrementTime.accept(900);
+        state.noteModemControllerActivity();
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
+                CellSignalStrength.SIGNAL_STRENGTH_GREAT);
+        incrementTime.accept(123);
+        state.setModemState(ModemState.RECEIVING);
+        incrementTime.accept(333);
+        state.setModemState(ModemState.TRANSMITTING);
+        incrementTime.accept(1000);
+        state.setModemState(ModemState.RECEIVING);
+        incrementTime.accept(555);
+        state.noteModemControllerActivity();
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        // Change in the signal strength of nonactive RAT should not affect anything.
+        state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER,
+                CellSignalStrength.SIGNAL_STRENGTH_POOR);
+        incrementTime.accept(631);
+        state.setModemState(ModemState.TRANSMITTING);
+        incrementTime.accept(321);
+        state.setModemState(ModemState.RECEIVING);
+        incrementTime.accept(99);
+        state.noteModemControllerActivity();
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        // Changing to OTHER Rat should start tracking the poor signal strength.
+        state.setRatType(TelephonyManager.NETWORK_TYPE_CDMA,
+                BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER);
+        incrementTime.accept(1200);
+        state.noteModemControllerActivity();
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        // Noting frequency change should not affect non NR Rat.
+        state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_HIGH);
+        incrementTime.accept(444);
+        state.setModemState(ModemState.TRANSMITTING);
+        incrementTime.accept(1300);
+        state.noteModemControllerActivity();
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        // Now the NR Rat, HIGH frequency range, good signal strength should start counting.
+        state.setRatType(TelephonyManager.NETWORK_TYPE_NR, BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR);
+        incrementTime.accept(1400);
+        state.noteModemControllerActivity();
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        // Frequency changed to low.
+        state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_LOW);
+        incrementTime.accept(852);
+        state.setModemState(ModemState.RECEIVING);
+        incrementTime.accept(157);
+        state.setModemState(ModemState.TRANSMITTING);
+        incrementTime.accept(1500);
+        state.noteModemControllerActivity();
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
+
+        // Modem no longer active, should not be tracking any more.
+        state.setModemActive(false);
+        incrementTime.accept(1500);
+        state.noteModemControllerActivity();
+        checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs,
+                expectedTxDurationsMs, bi, state.currentTimeMs);
     }
 
     private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) {
@@ -1538,28 +1786,124 @@
     }
 
     private void checkPerStateActiveRadioDurations(long[][][] expectedDurationsMs,
+            long[][] expectedRxDurationsMs, long[][][] expectedTxDurationsMs,
             BatteryStatsImpl bi, long currentTimeMs) {
         for (int rat = 0; rat < expectedDurationsMs.length; rat++) {
             final long[][] expectedRatDurationsMs = expectedDurationsMs[rat];
             for (int freq = 0; freq < expectedRatDurationsMs.length; freq++) {
+                final long expectedRxDurationMs = expectedRxDurationsMs[rat][freq];
+
+                // Build a verbose fail message, just in case.
+                final StringBuilder rxFailSb = new StringBuilder();
+                rxFailSb.append("Wrong time in Rx state for RAT:");
+                rxFailSb.append(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NAMES[rat]);
+                rxFailSb.append(", frequency:");
+                rxFailSb.append(ServiceState.frequencyRangeToString(freq));
+                assertEquals(rxFailSb.toString(), expectedRxDurationMs,
+                        bi.getActiveRxRadioDurationMs(rat, freq, currentTimeMs));
+
                 final long[] expectedFreqDurationsMs = expectedRatDurationsMs[freq];
                 for (int strength = 0; strength < expectedFreqDurationsMs.length; strength++) {
                     final long expectedSignalStrengthDurationMs = expectedFreqDurationsMs[strength];
+                    final long expectedTxDurationMs = expectedTxDurationsMs[rat][freq][strength];
                     final long actualDurationMs = bi.getActiveRadioDurationMs(rat, freq,
                             strength, currentTimeMs);
 
-                    // Build a verbose fail message, just in case.
-                    final StringBuilder sb = new StringBuilder();
-                    sb.append("Wrong time in state for RAT:");
-                    sb.append(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NAMES[rat]);
-                    sb.append(", frequency:");
-                    sb.append(ServiceState.frequencyRangeToString(freq));
-                    sb.append(", strength:");
-                    sb.append(strength);
+                    final StringBuilder failSb = new StringBuilder();
+                    failSb.append("Wrong time in state for RAT:");
+                    failSb.append(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NAMES[rat]);
+                    failSb.append(", frequency:");
+                    failSb.append(ServiceState.frequencyRangeToString(freq));
+                    failSb.append(", strength:");
+                    failSb.append(strength);
+                    assertEquals(failSb.toString(), expectedSignalStrengthDurationMs,
+                            actualDurationMs);
 
-                    assertEquals(sb.toString(), expectedSignalStrengthDurationMs, actualDurationMs);
+                    final StringBuilder txFailSb = new StringBuilder();
+                    txFailSb.append("Wrong time in Tx state for RAT:");
+                    txFailSb.append(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NAMES[rat]);
+                    txFailSb.append(", frequency:");
+                    txFailSb.append(ServiceState.frequencyRangeToString(freq));
+                    txFailSb.append(", strength:");
+                    txFailSb.append(strength);
+                    assertEquals(txFailSb.toString(), expectedTxDurationMs,
+                            bi.getActiveTxRadioDurationMs(rat, freq, strength, currentTimeMs));
                 }
             }
         }
     }
+
+    private class ModemAndBatteryState {
+        public long currentTimeMs = 100;
+        public boolean onBattery = false;
+        public boolean modemActive = false;
+        @Annotation.NetworkType
+        public int currentNetworkDataType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+        @BatteryStats.RadioAccessTechnology
+        public int currentRat = BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER;
+        @ServiceState.FrequencyRange
+        public int currentFrequencyRange = ServiceState.FREQUENCY_RANGE_UNKNOWN;
+        public SparseIntArray currentSignalStrengths = new SparseIntArray();
+        public ModemState modemState = ModemState.SLEEP;
+        public ModemActivityInfo modemActivityInfo;
+
+        private final MockBatteryStatsImpl mBsi;
+
+        ModemAndBatteryState(MockBatteryStatsImpl bsi, ModemActivityInfo mai) {
+            mBsi = bsi;
+            modemActivityInfo = mai;
+        }
+
+        void setOnBattery(boolean onBattery) {
+            this.onBattery = onBattery;
+            mBsi.updateTimeBasesLocked(onBattery, Display.STATE_OFF, currentTimeMs * 1000,
+                    currentTimeMs * 1000);
+            mBsi.setOnBatteryInternal(onBattery);
+            noteModemControllerActivity();
+        }
+
+        void setModemActive(boolean active) {
+            modemActive = active;
+            final int state = active ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
+                    : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+            mBsi.noteMobileRadioPowerStateLocked(state, currentTimeMs * 1000_000L, UID);
+            noteModemControllerActivity();
+        }
+
+        void setRatType(@Annotation.NetworkType int dataType,
+                @BatteryStats.RadioAccessTechnology int rat) {
+            currentNetworkDataType = dataType;
+            currentRat = rat;
+            mBsi.notePhoneDataConnectionStateLocked(dataType, true, ServiceState.STATE_IN_SERVICE,
+                    currentFrequencyRange);
+        }
+
+        void setFrequencyRange(@ServiceState.FrequencyRange int frequency) {
+            currentFrequencyRange = frequency;
+            mBsi.notePhoneDataConnectionStateLocked(currentNetworkDataType, true,
+                    ServiceState.STATE_IN_SERVICE, frequency);
+        }
+
+        void setSignalStrength(@BatteryStats.RadioAccessTechnology int rat, int strength) {
+            currentSignalStrengths.put(rat, strength);
+            final int size = currentSignalStrengths.size();
+            final int newestGenSignalStrength = currentSignalStrengths.valueAt(size - 1);
+            mBsi.notePhoneSignalStrengthLocked(newestGenSignalStrength, currentSignalStrengths);
+        }
+
+        void setModemState(ModemState state) {
+            modemState = state;
+        }
+
+        void noteModemControllerActivity() {
+            if (modemActivityInfo == null) return;
+            modemActivityInfo.setTimestamp(currentTimeMs);
+            ModemActivityInfo copy = new ModemActivityInfo(modemActivityInfo.getTimestampMillis(),
+                    modemActivityInfo.getSleepTimeMillis(), modemActivityInfo.getIdleTimeMillis(),
+                    modemActivityInfo.getTransmitTimeMillis().clone(),
+                    modemActivityInfo.getReceiveTimeMillis());
+            mBsi.noteModemControllerActivity(copy, POWER_DATA_UNAVAILABLE,
+                    currentTimeMs, currentTimeMs, mNetworkStatsManager);
+        }
+    }
 }
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredential.java b/identity/java/android/security/identity/CredstoreIdentityCredential.java
index 8e01105..c591c87 100644
--- a/identity/java/android/security/identity/CredstoreIdentityCredential.java
+++ b/identity/java/android/security/identity/CredstoreIdentityCredential.java
@@ -38,8 +38,9 @@
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.Collection;
-import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 
 import javax.crypto.BadPaddingException;
@@ -227,7 +228,7 @@
                 throw new RuntimeException("Error decoding certificates", e);
             }
 
-            LinkedList<X509Certificate> x509Certs = new LinkedList<>();
+            ArrayList<X509Certificate> x509Certs = new ArrayList<>();
             for (Certificate cert : certs) {
                 x509Certs.add((X509Certificate) cert);
             }
@@ -384,7 +385,7 @@
     public @NonNull Collection<X509Certificate> getAuthKeysNeedingCertification() {
         try {
             AuthKeyParcel[] authKeyParcels = mBinder.getAuthKeysNeedingCertification();
-            LinkedList<X509Certificate> x509Certs = new LinkedList<>();
+            ArrayList<X509Certificate> x509Certs = new ArrayList<>();
             CertificateFactory factory = CertificateFactory.getInstance("X.509");
             for (AuthKeyParcel authKeyParcel : authKeyParcels) {
                 Collection<? extends Certificate> certs = null;
diff --git a/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java b/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java
index d2e7984..1ad70ed 100644
--- a/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java
+++ b/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java
@@ -25,8 +25,9 @@
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
+import java.util.ArrayList;
 import java.util.Collection;
-import java.util.LinkedList;
+import java.util.List;
 
 class CredstoreWritableIdentityCredential extends WritableIdentityCredential {
 
@@ -61,7 +62,7 @@
                 throw new RuntimeException("Error decoding certificates", e);
             }
 
-            LinkedList<X509Certificate> x509Certs = new LinkedList<>();
+            ArrayList<X509Certificate> x509Certs = new ArrayList<>();
             for (Certificate cert : certs) {
                 x509Certs.add((X509Certificate) cert);
             }
diff --git a/identity/java/android/security/identity/PersonalizationData.java b/identity/java/android/security/identity/PersonalizationData.java
index b34f250..bdb00fdf 100644
--- a/identity/java/android/security/identity/PersonalizationData.java
+++ b/identity/java/android/security/identity/PersonalizationData.java
@@ -18,10 +18,11 @@
 
 import android.annotation.NonNull;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashMap;
-import java.util.LinkedList;
+import java.util.List;
 
 /**
  * An object that holds personalization data.
@@ -38,7 +39,7 @@
     private PersonalizationData() {
     }
 
-    private LinkedList<AccessControlProfile> mProfiles = new LinkedList<>();
+    private ArrayList<AccessControlProfile> mProfiles = new ArrayList<>();
 
     private LinkedHashMap<String, NamespaceData> mNamespaces = new LinkedHashMap<>();
 
diff --git a/libs/WindowManager/Shell/res/values-television/config.xml b/libs/WindowManager/Shell/res/values-television/config.xml
new file mode 100644
index 0000000..552048e
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values-television/config.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!-- These resources are around just to allow their values to be customized
+     for TV products.  Do not translate. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- The percentage of the screen width to use for the default width or height of
+         picture-in-picture windows. Regardless of the percent set here, calculated size will never
+         be smaller than @dimen/default_minimal_size_pip_resizable_task. -->
+    <item name="config_pictureInPictureDefaultSizePercent" format="float" type="dimen">0.2</item>
+
+    <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows.
+         These values are in DPs and will be converted to pixel sizes internally. -->
+    <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">
+        24x24
+    </string>
+
+    <!-- The default gravity for the picture-in-picture window.
+         Currently, this maps to Gravity.BOTTOM | Gravity.RIGHT -->
+    <integer name="config_defaultPictureInPictureGravity">0x55</integer>
+</resources>
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 1b8032b..d416c06 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -70,4 +70,30 @@
 
     <!-- Animation duration when exit starting window: reveal app -->
     <integer name="starting_window_app_reveal_anim_duration">266</integer>
+
+    <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows.
+         These values are in DPs and will be converted to pixel sizes internally. -->
+    <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">
+        16x16
+    </string>
+
+    <!-- The percentage of the screen width to use for the default width or height of
+         picture-in-picture windows. Regardless of the percent set here, calculated size will never
+         be smaller than @dimen/default_minimal_size_pip_resizable_task. -->
+    <item name="config_pictureInPictureDefaultSizePercent" format="float" type="dimen">0.23</item>
+
+    <!-- The default aspect ratio for picture-in-picture windows. -->
+    <item name="config_pictureInPictureDefaultAspectRatio" format="float" type="dimen">
+        1.777778
+    </item>
+
+    <!-- This is the limit for the max and min aspect ratio (1 / this value) at which the min size
+         will be used instead of an adaptive size based loosely on area. -->
+    <item name="config_pictureInPictureAspectRatioLimitForMinSize" format="float" type="dimen">
+        1.777778
+    </item>
+
+    <!-- The default gravity for the picture-in-picture window.
+         Currently, this maps to Gravity.BOTTOM | Gravity.RIGHT -->
+    <integer name="config_defaultPictureInPictureGravity">0x55</integer>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 8a8231d..2c96786b 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -18,8 +18,8 @@
     <dimen name="dismiss_circle_size">96dp</dimen>
     <dimen name="dismiss_circle_small">60dp</dimen>
 
-    <!-- The height of the gradient indicating the dismiss edge when moving a PIP. -->
-    <dimen name="floating_dismiss_gradient_height">250dp</dimen>
+    <!-- The height of the gradient indicating the dismiss edge when moving a PIP or bubble. -->
+    <dimen name="floating_dismiss_gradient_height">548dp</dimen>
 
     <!-- The padding around a PiP actions. -->
     <dimen name="pip_action_padding">16dp</dimen>
@@ -129,6 +129,9 @@
     <dimen name="bubble_dismiss_encircle_size">52dp</dimen>
     <!-- Padding around the view displayed when the bubble is expanded -->
     <dimen name="bubble_expanded_view_padding">16dp</dimen>
+    <!-- Padding for the edge of the expanded view that is closest to the edge of the screen used
+         when displaying in landscape on a large screen. -->
+    <dimen name="bubble_expanded_view_largescreen_landscape_padding">128dp</dimen>
     <!-- This should be at least the size of bubble_expanded_view_padding; it is used to include
          a slight touch slop around the expanded view. -->
     <dimen name="bubble_expanded_view_slop">8dp</dimen>
@@ -251,4 +254,14 @@
 
     <!-- The distance of the shift icon when early exit starting window. -->
     <dimen name="starting_surface_early_exit_icon_distance">32dp</dimen>
+
+    <!-- The default minimal size of a PiP task, in both dimensions. -->
+    <dimen name="default_minimal_size_pip_resizable_task">108dp</dimen>
+
+    <!--
+      The overridable minimal size of a PiP task, in both dimensions.
+      Different from default_minimal_size_pip_resizable_task, this is to limit the dimension
+      when the pinned stack size is overridden by app via minWidth/minHeight.
+    -->
+    <dimen name="overridable_minimal_size_pip_resizable_task">48dp</dimen>
 </resources>
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 241f1a7..d0138a4 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
@@ -47,7 +47,10 @@
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
@@ -131,6 +134,10 @@
     public static final String RIGHT_POSITION = "Right";
     public static final String BOTTOM_POSITION = "Bottom";
 
+    // Should match with PhoneWindowManager
+    private static final String SYSTEM_DIALOG_REASON_KEY = "reason";
+    private static final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav";
+
     private final Context mContext;
     private final BubblesImpl mImpl = new BubblesImpl();
     private Bubbles.BubbleExpandListener mExpandListener;
@@ -675,6 +682,7 @@
 
         try {
             mAddedToWindowManager = true;
+            registerBroadcastReceiver();
             mBubbleData.getOverflow().initialize(this);
             mWindowManager.addView(mStackView, mWmLayoutParams);
             mStackView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
@@ -717,6 +725,7 @@
 
         try {
             mAddedToWindowManager = false;
+            mContext.unregisterReceiver(mBroadcastReceiver);
             if (mStackView != null) {
                 mWindowManager.removeView(mStackView);
                 mBubbleData.getOverflow().cleanUpExpandedState();
@@ -730,11 +739,34 @@
         }
     }
 
+    private void registerBroadcastReceiver() {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+        filter.addAction(Intent.ACTION_SCREEN_OFF);
+        mContext.registerReceiver(mBroadcastReceiver, filter);
+    }
+
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!isStackExpanded()) return; // Nothing to do
+
+            String action = intent.getAction();
+            String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
+            if ((Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
+                    && SYSTEM_DIALOG_REASON_GESTURE_NAV.equals(reason))
+                    || Intent.ACTION_SCREEN_OFF.equals(action)) {
+                mMainExecutor.execute(() -> collapseStack());
+            }
+        }
+    };
+
     /**
      * Called by the BubbleStackView and whenever all bubbles have animated out, and none have been
      * added in the meantime.
      */
-    void onAllBubblesAnimatedOut() {
+    @VisibleForTesting
+    public void onAllBubblesAnimatedOut() {
         if (mStackView != null) {
             mStackView.setVisibility(INVISIBLE);
             removeFromWindowManagerMaybe();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 75b19fb..bc0db36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -67,7 +67,11 @@
     /** The max percent of screen width to use for the flyout on phone. */
     public static final float FLYOUT_MAX_WIDTH_PERCENT = 0.6f;
     /** The percent of screen width that should be used for the expanded view on a large screen. **/
-    public static final float EXPANDED_VIEW_LARGE_SCREEN_WIDTH_PERCENT = 0.72f;
+    private static final float EXPANDED_VIEW_LARGE_SCREEN_LANDSCAPE_WIDTH_PERCENT = 0.48f;
+    /** The percent of screen width that should be used for the expanded view on a large screen. **/
+    private static final float EXPANDED_VIEW_LARGE_SCREEN_PORTRAIT_WIDTH_PERCENT = 0.70f;
+    /** The percent of screen width that should be used for the expanded view on a small tablet. **/
+    private static final float EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT = 0.72f;
 
     private Context mContext;
     private WindowManager mWindowManager;
@@ -77,6 +81,7 @@
     private boolean mImeVisible;
     private int mImeHeight;
     private boolean mIsLargeScreen;
+    private boolean mIsSmallTablet;
 
     private Rect mPositionRect;
     private int mDefaultMaxBubbles;
@@ -86,7 +91,8 @@
 
     private int mExpandedViewMinHeight;
     private int mExpandedViewLargeScreenWidth;
-    private int mExpandedViewLargeScreenInset;
+    private int mExpandedViewLargeScreenInsetClosestEdge;
+    private int mExpandedViewLargeScreenInsetFurthestEdge;
 
     private int mOverflowWidth;
     private int mExpandedViewPadding;
@@ -127,17 +133,26 @@
                 | WindowInsets.Type.statusBars()
                 | WindowInsets.Type.displayCutout());
 
-        mIsLargeScreen = mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600;
+        final Rect bounds = windowMetrics.getBounds();
+        Configuration config = mContext.getResources().getConfiguration();
+        mIsLargeScreen = config.smallestScreenWidthDp >= 600;
+        if (mIsLargeScreen) {
+            float largestEdgeDp = Math.max(config.screenWidthDp, config.screenHeightDp);
+            mIsSmallTablet = largestEdgeDp < 960;
+        } else {
+            mIsSmallTablet = false;
+        }
 
         if (BubbleDebugConfig.DEBUG_POSITIONER) {
             Log.w(TAG, "update positioner:"
                     + " rotation: " + mRotation
                     + " insets: " + insets
                     + " isLargeScreen: " + mIsLargeScreen
-                    + " bounds: " + windowMetrics.getBounds()
+                    + " isSmallTablet: " + mIsSmallTablet
+                    + " bounds: " + bounds
                     + " showingInTaskbar: " + mShowingInTaskbar);
         }
-        updateInternal(mRotation, insets, windowMetrics.getBounds());
+        updateInternal(mRotation, insets, bounds);
     }
 
     /**
@@ -172,11 +187,31 @@
         mSpacingBetweenBubbles = res.getDimensionPixelSize(R.dimen.bubble_spacing);
         mDefaultMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
         mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
-        mExpandedViewLargeScreenWidth = (int) (bounds.width()
-                * EXPANDED_VIEW_LARGE_SCREEN_WIDTH_PERCENT);
-        mExpandedViewLargeScreenInset = mIsLargeScreen
-                ? (bounds.width() - mExpandedViewLargeScreenWidth) / 2
-                : mExpandedViewPadding;
+        if (mIsSmallTablet) {
+            mExpandedViewLargeScreenWidth = (int) (bounds.width()
+                    * EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT);
+        } else {
+            mExpandedViewLargeScreenWidth = isLandscape()
+                    ? (int) (bounds.width() * EXPANDED_VIEW_LARGE_SCREEN_LANDSCAPE_WIDTH_PERCENT)
+                    : (int) (bounds.width() * EXPANDED_VIEW_LARGE_SCREEN_PORTRAIT_WIDTH_PERCENT);
+        }
+        if (mIsLargeScreen) {
+            if (isLandscape() && !mIsSmallTablet) {
+                mExpandedViewLargeScreenInsetClosestEdge = res.getDimensionPixelSize(
+                        R.dimen.bubble_expanded_view_largescreen_landscape_padding);
+                mExpandedViewLargeScreenInsetFurthestEdge = bounds.width()
+                        - mExpandedViewLargeScreenInsetClosestEdge
+                        - mExpandedViewLargeScreenWidth;
+            } else {
+                final int centeredInset = (bounds.width() - mExpandedViewLargeScreenWidth) / 2;
+                mExpandedViewLargeScreenInsetClosestEdge = centeredInset;
+                mExpandedViewLargeScreenInsetFurthestEdge = centeredInset;
+            }
+        } else {
+            mExpandedViewLargeScreenInsetClosestEdge = mExpandedViewPadding;
+            mExpandedViewLargeScreenInsetFurthestEdge = mExpandedViewPadding;
+        }
+
         mOverflowWidth = mIsLargeScreen
                 ? mExpandedViewLargeScreenWidth
                 : res.getDimensionPixelSize(
@@ -328,15 +363,18 @@
     public int[] getExpandedViewContainerPadding(boolean onLeft, boolean isOverflow) {
         final int pointerTotalHeight = mPointerHeight - mPointerOverlap;
         if (mIsLargeScreen) {
+            // Note:
+            // If we're in portrait OR if we're a small tablet, then the two insets values will
+            // be equal. If we're landscape and a large tablet, the two values will be different.
             // [left, top, right, bottom]
             mPaddings[0] = onLeft
-                    ? mExpandedViewLargeScreenInset - pointerTotalHeight
-                    : mExpandedViewLargeScreenInset;
+                    ? mExpandedViewLargeScreenInsetClosestEdge - pointerTotalHeight
+                    : mExpandedViewLargeScreenInsetFurthestEdge;
             mPaddings[1] = 0;
             mPaddings[2] = onLeft
-                    ? mExpandedViewLargeScreenInset
-                    : mExpandedViewLargeScreenInset - pointerTotalHeight;
-            // Overflow doesn't show manage button / get padding from it so add padding here for it
+                    ? mExpandedViewLargeScreenInsetFurthestEdge
+                    : mExpandedViewLargeScreenInsetClosestEdge - pointerTotalHeight;
+            // Overflow doesn't show manage button / get padding from it so add padding here
             mPaddings[3] = isOverflow ? mExpandedViewPadding : 0;
             return mPaddings;
         } else {
@@ -494,12 +532,13 @@
         float x;
         float y;
         if (showBubblesVertically()) {
+            int inset = mExpandedViewLargeScreenInsetClosestEdge;
             y = rowStart + positionInRow;
             int left = mIsLargeScreen
-                    ? mExpandedViewLargeScreenInset - mExpandedViewPadding - mBubbleSize
+                    ? inset - mExpandedViewPadding - mBubbleSize
                     : mPositionRect.left;
             int right = mIsLargeScreen
-                    ? mPositionRect.right - mExpandedViewLargeScreenInset + mExpandedViewPadding
+                    ? mPositionRect.right - inset + mExpandedViewPadding
                     : mPositionRect.right - mBubbleSize;
             x = state.onLeft
                     ? left
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt
index 74672a3..063dac3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt
@@ -16,11 +16,16 @@
 
 package com.android.wm.shell.bubbles
 
+import android.animation.ObjectAnimator
 import android.content.Context
-import android.graphics.drawable.TransitionDrawable
+import android.graphics.Color
+import android.graphics.drawable.GradientDrawable
+import android.util.IntProperty
 import android.view.Gravity
 import android.view.View
 import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.WindowInsets
 import android.widget.FrameLayout
 import androidx.dynamicanimation.animation.DynamicAnimation
 import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY
@@ -28,8 +33,6 @@
 import com.android.wm.shell.R
 import com.android.wm.shell.animation.PhysicsAnimator
 import com.android.wm.shell.common.DismissCircleView
-import android.view.WindowInsets
-import android.view.WindowManager
 
 /*
  * View that handles interactions between DismissCircleView and BubbleStackView.
@@ -41,9 +44,21 @@
 
     private val animator = PhysicsAnimator.getInstance(circle)
     private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY)
-    private val DISMISS_SCRIM_FADE_MS = 200
+    private val DISMISS_SCRIM_FADE_MS = 200L
     private var wm: WindowManager =
             context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+    private var gradientDrawable = createGradient()
+
+    private val GRADIENT_ALPHA: IntProperty<GradientDrawable> =
+            object : IntProperty<GradientDrawable>("alpha") {
+        override fun setValue(d: GradientDrawable, percent: Int) {
+            d.alpha = percent
+        }
+        override fun get(d: GradientDrawable): Int {
+            return d.alpha
+        }
+    }
+
     init {
         setLayoutParams(LayoutParams(
             ViewGroup.LayoutParams.MATCH_PARENT,
@@ -53,8 +68,7 @@
         setClipToPadding(false)
         setClipChildren(false)
         setVisibility(View.INVISIBLE)
-        setBackgroundResource(
-            R.drawable.floating_dismiss_gradient_transition)
+        setBackgroundDrawable(gradientDrawable)
 
         val targetSize: Int = resources.getDimensionPixelSize(R.dimen.dismiss_circle_size)
         addView(circle, LayoutParams(targetSize, targetSize,
@@ -71,7 +85,11 @@
         if (isShowing) return
         isShowing = true
         setVisibility(View.VISIBLE)
-        (getBackground() as TransitionDrawable).startTransition(DISMISS_SCRIM_FADE_MS)
+        val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA,
+                gradientDrawable.alpha, 255)
+        alphaAnim.setDuration(DISMISS_SCRIM_FADE_MS)
+        alphaAnim.start()
+
         animator.cancel()
         animator
             .spring(DynamicAnimation.TRANSLATION_Y, 0f, spring)
@@ -85,7 +103,10 @@
     fun hide() {
         if (!isShowing) return
         isShowing = false
-        (getBackground() as TransitionDrawable).reverseTransition(DISMISS_SCRIM_FADE_MS)
+        val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA,
+                gradientDrawable.alpha, 0)
+        alphaAnim.setDuration(DISMISS_SCRIM_FADE_MS)
+        alphaAnim.start()
         animator
             .spring(DynamicAnimation.TRANSLATION_Y, height.toFloat(),
                 spring)
@@ -93,6 +114,13 @@
             .start()
     }
 
+    /**
+     * Cancels the animator for the dismiss target.
+     */
+    fun cancelAnimators() {
+        animator.cancel()
+    }
+
     fun updateResources() {
         updatePadding()
         layoutParams.height = resources.getDimensionPixelSize(
@@ -104,6 +132,20 @@
         circle.requestLayout()
     }
 
+    private fun createGradient(): GradientDrawable {
+        val gradientColor = context.resources.getColor(android.R.color.system_neutral1_900)
+        val alpha = 0.7f * 255
+        val gradientColorWithAlpha = Color.argb(alpha.toInt(),
+                Color.red(gradientColor),
+                Color.green(gradientColor),
+                Color.blue(gradientColor))
+        val gd = GradientDrawable(
+                GradientDrawable.Orientation.BOTTOM_TOP,
+                intArrayOf(gradientColorWithAlpha, Color.TRANSPARENT))
+        gd.setAlpha(0)
+        return gd
+    }
+
     private fun updatePadding() {
         val insets: WindowInsets = wm.getCurrentWindowMetrics().getWindowInsets()
         val navInset = insets.getInsetsIgnoringVisibility(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index f7c92fe..8a482fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -144,21 +144,42 @@
         return Math.max(dividerInset, radius);
     }
 
-    /** Gets bounds of the primary split. */
+    /** Gets bounds of the primary split with screen based coordinate. */
     public Rect getBounds1() {
         return new Rect(mBounds1);
     }
 
-    /** Gets bounds of the secondary split. */
+    /** Gets bounds of the primary split with parent based coordinate. */
+    public Rect getRefBounds1() {
+        Rect outBounds = getBounds1();
+        outBounds.offset(-mRootBounds.left, -mRootBounds.top);
+        return outBounds;
+    }
+
+    /** Gets bounds of the secondary split with screen based coordinate. */
     public Rect getBounds2() {
         return new Rect(mBounds2);
     }
 
-    /** Gets bounds of divider window. */
+    /** Gets bounds of the secondary split with parent based coordinate. */
+    public Rect getRefBounds2() {
+        final Rect outBounds = getBounds2();
+        outBounds.offset(-mRootBounds.left, -mRootBounds.top);
+        return outBounds;
+    }
+
+    /** Gets bounds of divider window with screen based coordinate. */
     public Rect getDividerBounds() {
         return new Rect(mDividerBounds);
     }
 
+    /** Gets bounds of divider window with parent based coordinate. */
+    public Rect getRefDividerBounds() {
+        final Rect outBounds = getDividerBounds();
+        outBounds.offset(-mRootBounds.left, -mRootBounds.top);
+        return outBounds;
+    }
+
     /** Returns leash of the current divider bar. */
     @Nullable
     public SurfaceControl getDividerLeash() {
@@ -452,14 +473,17 @@
             SurfaceControl leash2, SurfaceControl dimLayer1, SurfaceControl dimLayer2) {
         final SurfaceControl dividerLeash = getDividerLeash();
         if (dividerLeash != null) {
-            t.setPosition(dividerLeash, mDividerBounds.left, mDividerBounds.top);
+            mTempRect.set(getRefDividerBounds());
+            t.setPosition(dividerLeash, mTempRect.left, mTempRect.top);
             // Resets layer of divider bar to make sure it is always on top.
             t.setLayer(dividerLeash, SPLIT_DIVIDER_LAYER);
         }
-        t.setPosition(leash1, mBounds1.left, mBounds1.top)
-                .setWindowCrop(leash1, mBounds1.width(), mBounds1.height());
-        t.setPosition(leash2, mBounds2.left, mBounds2.top)
-                .setWindowCrop(leash2, mBounds2.width(), mBounds2.height());
+        mTempRect.set(getRefBounds1());
+        t.setPosition(leash1, mTempRect.left, mTempRect.top)
+                .setWindowCrop(leash1, mTempRect.width(), mTempRect.height());
+        mTempRect.set(getRefBounds2());
+        t.setPosition(leash2, mTempRect.left, mTempRect.top)
+                .setWindowCrop(leash2, mTempRect.width(), mTempRect.height());
 
         if (mImePositionProcessor.adjustSurfaceLayoutForIme(
                 t, dividerLeash, leash1, leash2, dimLayer1, dimLayer2)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
index e29dde2..797df41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
@@ -32,6 +32,7 @@
 import android.util.TypedValue;
 import android.view.Gravity;
 
+import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayLayout;
 
 import java.io.PrintWriter;
@@ -76,15 +77,15 @@
     protected void reloadResources(Context context) {
         final Resources res = context.getResources();
         mDefaultAspectRatio = res.getFloat(
-                com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio);
+                R.dimen.config_pictureInPictureDefaultAspectRatio);
         mDefaultStackGravity = res.getInteger(
-                com.android.internal.R.integer.config_defaultPictureInPictureGravity);
+                R.integer.config_defaultPictureInPictureGravity);
         mDefaultMinSize = res.getDimensionPixelSize(
-                com.android.internal.R.dimen.default_minimal_size_pip_resizable_task);
+                R.dimen.default_minimal_size_pip_resizable_task);
         mOverridableMinSize = res.getDimensionPixelSize(
-                com.android.internal.R.dimen.overridable_minimal_size_pip_resizable_task);
+                R.dimen.overridable_minimal_size_pip_resizable_task);
         final String screenEdgeInsetsDpString = res.getString(
-                com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets);
+                R.string.config_defaultPictureInPictureScreenEdgeInsets);
         final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
                 ? Size.parseSize(screenEdgeInsetsDpString)
                 : null;
@@ -96,9 +97,9 @@
         mMaxAspectRatio = res.getFloat(
                 com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
         mDefaultSizePercent = res.getFloat(
-                com.android.internal.R.dimen.config_pictureInPictureDefaultSizePercent);
+                R.dimen.config_pictureInPictureDefaultSizePercent);
         mMaxAspectRatioForMinSize = res.getFloat(
-                com.android.internal.R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
+                R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
         mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index 180e3fb..d7322ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -138,8 +138,8 @@
         // destination are different.
         final float scale = srcW <= srcH ? (float) destW / srcW : (float) destH / srcH;
         final Rect crop = mTmpDestinationRect;
-        crop.set(0, 0, Transitions.ENABLE_SHELL_TRANSITIONS ? destH
-                : destW, Transitions.ENABLE_SHELL_TRANSITIONS ? destW : destH);
+        crop.set(0, 0, Transitions.SHELL_TRANSITIONS_ROTATION ? destH
+                : destW, Transitions.SHELL_TRANSITIONS_ROTATION ? destW : destH);
         // Inverse scale for crop to fit in screen coordinates.
         crop.scale(1 / scale);
         crop.offset(insets.left, insets.top);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 60aac68..91615fe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -97,7 +97,7 @@
      * meaningful if {@link #mInFixedRotation} is true.
      */
     @Surface.Rotation
-    private int mFixedRotation;
+    private int mEndFixedRotation;
     /** Whether the PIP window has fade out for fixed rotation. */
     private boolean mHasFadeOut;
 
@@ -153,7 +153,7 @@
         final TransitionInfo.Change currentPipChange = findCurrentPipChange(info);
         final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
         mInFixedRotation = fixedRotationChange != null;
-        mFixedRotation = mInFixedRotation
+        mEndFixedRotation = mInFixedRotation
                 ? fixedRotationChange.getEndFixedRotation()
                 : ROTATION_UNDEFINED;
 
@@ -257,7 +257,7 @@
         final ActivityManager.RunningTaskInfo taskInfo = mPipOrganizer.getTaskInfo();
         if (taskInfo != null) {
             startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(),
-                    new Rect(mExitDestinationBounds));
+                    new Rect(mExitDestinationBounds), Surface.ROTATION_0);
         }
         mExitDestinationBounds.setEmpty();
         mCurrentPipTaskToken = null;
@@ -332,30 +332,31 @@
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback,
             @NonNull TransitionInfo.Change pipChange) {
-        TransitionInfo.Change displayRotationChange = null;
-        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-            final TransitionInfo.Change change = info.getChanges().get(i);
-            if (change.getMode() == TRANSIT_CHANGE
-                    && (change.getFlags() & FLAG_IS_DISPLAY) != 0
-                    && change.getStartRotation() != change.getEndRotation()) {
-                displayRotationChange = change;
-                break;
-            }
-        }
-
-        if (displayRotationChange != null) {
-            // Exiting PIP to fullscreen with orientation change.
-            startExpandAndRotationAnimation(info, startTransaction, finishTransaction,
-                    finishCallback, displayRotationChange, pipChange);
-            return;
-        }
-
-        // When there is no rotation, we can simply expand the PIP window.
         mFinishCallback = (wct, wctCB) -> {
             mPipOrganizer.onExitPipFinished(pipChange.getTaskInfo());
             finishCallback.onTransitionFinished(wct, wctCB);
         };
 
+        // Check if it is Shell rotation.
+        if (Transitions.SHELL_TRANSITIONS_ROTATION) {
+            TransitionInfo.Change displayRotationChange = null;
+            for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                final TransitionInfo.Change change = info.getChanges().get(i);
+                if (change.getMode() == TRANSIT_CHANGE
+                        && (change.getFlags() & FLAG_IS_DISPLAY) != 0
+                        && change.getStartRotation() != change.getEndRotation()) {
+                    displayRotationChange = change;
+                    break;
+                }
+            }
+            if (displayRotationChange != null) {
+                // Exiting PIP to fullscreen with orientation change.
+                startExpandAndRotationAnimation(info, startTransaction, finishTransaction,
+                        displayRotationChange, pipChange);
+                return;
+            }
+        }
+
         // Set the initial frame as scaling the end to the start.
         final Rect destinationBounds = new Rect(pipChange.getEndAbsBounds());
         final Point offset = pipChange.getEndRelOffset();
@@ -364,13 +365,41 @@
         mSurfaceTransactionHelper.scale(startTransaction, pipChange.getLeash(),
                 destinationBounds, mPipBoundsState.getBounds());
         startTransaction.apply();
-        startExpandAnimation(pipChange.getTaskInfo(), pipChange.getLeash(), destinationBounds);
+
+        // Check if it is fixed rotation.
+        final int rotationDelta;
+        if (mInFixedRotation) {
+            final int startRotation = pipChange.getStartRotation();
+            final int endRotation = mEndFixedRotation;
+            rotationDelta = deltaRotation(startRotation, endRotation);
+            final Rect endBounds = new Rect(destinationBounds);
+
+            // Set the end frame since the display won't rotate until fixed rotation is finished
+            // in the next display change transition.
+            rotateBounds(endBounds, destinationBounds, rotationDelta);
+            final int degree, x, y;
+            if (rotationDelta == ROTATION_90) {
+                degree = 90;
+                x = destinationBounds.right;
+                y = destinationBounds.top;
+            } else {
+                degree = -90;
+                x = destinationBounds.left;
+                y = destinationBounds.bottom;
+            }
+            mSurfaceTransactionHelper.rotateAndScaleWithCrop(finishTransaction,
+                    pipChange.getLeash(), endBounds, endBounds, new Rect(), degree, x, y,
+                    true /* isExpanding */, rotationDelta == ROTATION_270 /* clockwise */);
+        } else {
+            rotationDelta = Surface.ROTATION_0;
+        }
+        startExpandAnimation(pipChange.getTaskInfo(), pipChange.getLeash(), destinationBounds,
+                rotationDelta);
     }
 
     private void startExpandAndRotationAnimation(@NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
-            @NonNull Transitions.TransitionFinishCallback finishCallback,
             @NonNull TransitionInfo.Change displayRotationChange,
             @NonNull TransitionInfo.Change pipChange) {
         final int rotateDelta = deltaRotation(displayRotationChange.getStartRotation(),
@@ -380,11 +409,6 @@
         final CounterRotatorHelper rotator = new CounterRotatorHelper();
         rotator.handleClosingChanges(info, startTransaction, displayRotationChange);
 
-        mFinishCallback = (wct, wctCB) -> {
-            mPipOrganizer.onExitPipFinished(pipChange.getTaskInfo());
-            finishCallback.onTransitionFinished(wct, wctCB);
-        };
-
         // Get the start bounds in new orientation.
         final Rect startBounds = new Rect(pipChange.getStartAbsBounds());
         rotateBounds(startBounds, displayRotationChange.getStartAbsBounds(), rotateDelta);
@@ -425,12 +449,11 @@
     }
 
     private void startExpandAnimation(final TaskInfo taskInfo, final SurfaceControl leash,
-            final Rect destinationBounds) {
-        PipAnimationController.PipTransitionAnimator animator =
+            final Rect destinationBounds, final int rotationDelta) {
+        final PipAnimationController.PipTransitionAnimator animator =
                 mPipAnimationController.getAnimator(taskInfo, leash, mPipBoundsState.getBounds(),
                         mPipBoundsState.getBounds(), destinationBounds, null,
-                        TRANSITION_DIRECTION_LEAVE_PIP, 0 /* startingAngle */, Surface.ROTATION_0);
-
+                        TRANSITION_DIRECTION_LEAVE_PIP, 0 /* startingAngle */, rotationDelta);
         animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP)
                 .setPipAnimationCallback(mPipAnimationCallback)
                 .setDuration(mEnterExitAnimationDuration)
@@ -526,7 +549,7 @@
 
         mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
         mFinishCallback = finishCallback;
-        final int endRotation = mInFixedRotation ? mFixedRotation : enterPip.getEndRotation();
+        final int endRotation = mInFixedRotation ? mEndFixedRotation : enterPip.getEndRotation();
         return startEnterAnimation(enterPip.getTaskInfo(), enterPip.getLeash(),
                 startTransaction, finishTransaction, enterPip.getStartRotation(),
                 endRotation);
@@ -545,8 +568,8 @@
                 taskInfo.pictureInPictureParams, currentBounds);
         if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) {
             // Need to get the bounds of new rotation in old rotation for fixed rotation,
-            sourceHintRect = computeRotatedBounds(rotationDelta, startRotation, endRotation,
-                    taskInfo, TRANSITION_DIRECTION_TO_PIP, destinationBounds, sourceHintRect);
+            computeEnterPipRotatedBounds(rotationDelta, startRotation, endRotation, taskInfo,
+                    destinationBounds, sourceHintRect);
         }
         PipAnimationController.PipTransitionAnimator animator;
         // Set corner radius for entering pip.
@@ -583,8 +606,6 @@
             startTransaction.setMatrix(leash, tmpTransform, new float[9]);
         }
         if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
-            // Reverse the rotation for Shell transition animation.
-            rotationDelta = deltaRotation(rotationDelta, 0);
             animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds,
                     currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
                     0 /* startingAngle */, rotationDelta);
@@ -617,33 +638,22 @@
         return true;
     }
 
-    /** Computes destination bounds in old rotation and returns source hint rect if available. */
-    @Nullable
-    private Rect computeRotatedBounds(int rotationDelta, int startRotation, int endRotation,
-            TaskInfo taskInfo, int direction, Rect outDestinationBounds,
-            @Nullable Rect sourceHintRect) {
-        if (direction == TRANSITION_DIRECTION_TO_PIP) {
-            mPipBoundsState.getDisplayLayout().rotateTo(mContext.getResources(), endRotation);
-            final Rect displayBounds = mPipBoundsState.getDisplayBounds();
-            outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
-            // Transform the destination bounds to current display coordinates.
-            rotateBounds(outDestinationBounds, displayBounds, endRotation, startRotation);
-            // When entering PiP (from button navigation mode), adjust the source rect hint by
-            // display cutout if applicable.
-            if (sourceHintRect != null && taskInfo.displayCutoutInsets != null) {
-                if (rotationDelta == Surface.ROTATION_270) {
-                    sourceHintRect.offset(taskInfo.displayCutoutInsets.left,
-                            taskInfo.displayCutoutInsets.top);
-                }
+    /** Computes destination bounds in old rotation and updates source hint rect if available. */
+    private void computeEnterPipRotatedBounds(int rotationDelta, int startRotation, int endRotation,
+            TaskInfo taskInfo, Rect outDestinationBounds, @Nullable Rect outSourceHintRect) {
+        mPipBoundsState.getDisplayLayout().rotateTo(mContext.getResources(), endRotation);
+        final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+        outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
+        // Transform the destination bounds to current display coordinates.
+        rotateBounds(outDestinationBounds, displayBounds, endRotation, startRotation);
+        // When entering PiP (from button navigation mode), adjust the source rect hint by
+        // display cutout if applicable.
+        if (outSourceHintRect != null && taskInfo.displayCutoutInsets != null) {
+            if (rotationDelta == Surface.ROTATION_270) {
+                outSourceHintRect.offset(taskInfo.displayCutoutInsets.left,
+                        taskInfo.displayCutoutInsets.top);
             }
-        } else if (direction == TRANSITION_DIRECTION_LEAVE_PIP) {
-            final Rect rotatedDestinationBounds = new Rect(outDestinationBounds);
-            rotateBounds(rotatedDestinationBounds, mPipBoundsState.getDisplayBounds(),
-                    rotationDelta);
-            return PipBoundsAlgorithm.getValidSourceHintRect(taskInfo.pictureInPictureParams,
-                    rotatedDestinationBounds);
         }
-        return sourceHintRect;
     }
 
     private void startExitToSplitAnimation(TransitionInfo info,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
index 915c593..3115f8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
@@ -20,27 +20,20 @@
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.drawable.TransitionDrawable;
-import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.WindowManager;
-import android.widget.FrameLayout;
 
 import androidx.annotation.NonNull;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.wm.shell.R;
-import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.bubbles.DismissView;
 import com.android.wm.shell.common.DismissCircleView;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
@@ -56,9 +49,6 @@
     /* The multiplier to apply scale the target size by when applying the magnetic field radius */
     private static final float MAGNETIC_FIELD_RADIUS_MULTIPLIER = 1.25f;
 
-    /** Duration of the dismiss scrim fading in/out. */
-    private static final int DISMISS_TRANSITION_DURATION_MS = 200;
-
     /**
      * MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move
      * PIP.
@@ -69,7 +59,7 @@
      * Container for the dismiss circle, so that it can be animated within the container via
      * translation rather than within the WindowManager via slow layout animations.
      */
-    private ViewGroup mTargetViewContainer;
+    private DismissView mTargetViewContainer;
 
     /** Circle view used to render the dismiss target. */
     private DismissCircleView mTargetView;
@@ -79,16 +69,6 @@
      */
     private MagnetizedObject.MagneticTarget mMagneticTarget;
 
-    /**
-     * PhysicsAnimator instance for animating the dismiss target in/out.
-     */
-    private PhysicsAnimator<View> mMagneticTargetAnimator;
-
-    /** Default configuration to use for springing the dismiss target in/out. */
-    private final PhysicsAnimator.SpringConfig mTargetSpringConfig =
-            new PhysicsAnimator.SpringConfig(
-                    SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
-
     // Allow dragging the PIP to a location to close it
     private boolean mEnableDismissDragToEdge;
 
@@ -125,12 +105,8 @@
             cleanUpDismissTarget();
         }
 
-        mTargetView = new DismissCircleView(mContext);
-        mTargetViewContainer = new FrameLayout(mContext);
-        mTargetViewContainer.setBackgroundDrawable(
-                mContext.getDrawable(R.drawable.floating_dismiss_gradient_transition));
-        mTargetViewContainer.setClipChildren(false);
-        mTargetViewContainer.addView(mTargetView);
+        mTargetViewContainer = new DismissView(mContext);
+        mTargetView = mTargetViewContainer.getCircle();
         mTargetViewContainer.setOnApplyWindowInsetsListener((view, windowInsets) -> {
             if (!windowInsets.equals(mWindowInsets)) {
                 mWindowInsets = windowInsets;
@@ -187,7 +163,6 @@
             }
         });
 
-        mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView);
     }
 
     @Override
@@ -213,19 +188,13 @@
         if (mTargetView == null) {
             return;
         }
+        if (mTargetViewContainer != null) {
+            mTargetViewContainer.updateResources();
+        }
 
         final Resources res = mContext.getResources();
         mTargetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
         mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
-        final WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
-        final Insets navInset = insets.getInsetsIgnoringVisibility(
-                WindowInsets.Type.navigationBars());
-        final FrameLayout.LayoutParams newParams =
-                new FrameLayout.LayoutParams(mTargetSize, mTargetSize);
-        newParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
-        newParams.bottomMargin = navInset.bottom + mContext.getResources().getDimensionPixelSize(
-                R.dimen.floating_dismiss_bottom_margin);
-        mTargetView.setLayoutParams(newParams);
 
         // Set the magnetic field radius equal to the target size from the center of the target
         setMagneticFieldRadiusPercent(mMagneticFieldRadiusPercent);
@@ -261,7 +230,7 @@
     /** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */
     public void createOrUpdateDismissTarget() {
         if (!mTargetViewContainer.isAttachedToWindow()) {
-            mMagneticTargetAnimator.cancel();
+            mTargetViewContainer.cancelAnimators();
 
             mTargetViewContainer.setVisibility(View.INVISIBLE);
             mTargetViewContainer.getViewTreeObserver().removeOnPreDrawListener(this);
@@ -312,18 +281,8 @@
         createOrUpdateDismissTarget();
 
         if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
-            mTargetView.setTranslationY(mTargetViewContainer.getHeight());
-            mTargetViewContainer.setVisibility(View.VISIBLE);
             mTargetViewContainer.getViewTreeObserver().addOnPreDrawListener(this);
-
-            // Cancel in case we were in the middle of animating it out.
-            mMagneticTargetAnimator.cancel();
-            mMagneticTargetAnimator
-                    .spring(DynamicAnimation.TRANSLATION_Y, 0f, mTargetSpringConfig)
-                    .start();
-
-            ((TransitionDrawable) mTargetViewContainer.getBackground()).startTransition(
-                    DISMISS_TRANSITION_DURATION_MS);
+            mTargetViewContainer.show();
         }
     }
 
@@ -332,16 +291,7 @@
         if (!mEnableDismissDragToEdge) {
             return;
         }
-
-        mMagneticTargetAnimator
-                .spring(DynamicAnimation.TRANSLATION_Y,
-                        mTargetViewContainer.getHeight(),
-                        mTargetSpringConfig)
-                .withEndActions(() -> mTargetViewContainer.setVisibility(View.GONE))
-                .start();
-
-        ((TransitionDrawable) mTargetViewContainer.getBackground()).reverseTransition(
-                DISMISS_TRANSITION_DURATION_MS);
+        mTargetViewContainer.hide();
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index 69d6c9e..32ebe2d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -33,6 +33,7 @@
 import android.util.Log;
 import android.view.SurfaceControl;
 import android.view.SyncRtSurfaceTransactionApplier;
+import android.view.WindowManagerGlobal;
 
 import androidx.annotation.Nullable;
 
@@ -143,7 +144,6 @@
         mSystemWindows.addView(mPipMenuView,
                 getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
                 0, SHELL_ROOT_LAYER_PIP);
-        mPipMenuView.setFocusGrantToken(mSystemWindows.getFocusGrantToken(mPipMenuView));
     }
 
     @Override
@@ -164,6 +164,7 @@
                 t.setPosition(menuSurfaceControl, menuBounds.left, menuBounds.top);
                 t.apply();
             }
+            grantPipMenuFocus(true);
             mPipMenuView.show(mInMoveMode, mDelegate.getPipGravity());
         }
     }
@@ -194,8 +195,9 @@
             if (DEBUG) Log.d(TAG, "hideMenu()");
         }
 
-        mPipMenuView.hide(mInMoveMode);
+        mPipMenuView.hide();
         if (!mInMoveMode) {
+            grantPipMenuFocus(false);
             mDelegate.closeMenu();
         }
     }
@@ -453,4 +455,15 @@
 
         void closePip();
     }
+
+    private void grantPipMenuFocus(boolean grantFocus) {
+        if (DEBUG) Log.d(TAG, "grantWindowFocus(" + grantFocus + ")");
+
+        try {
+            WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
+                    mSystemWindows.getFocusGrantToken(mPipMenuView), grantFocus);
+        } catch (Exception e) {
+            Log.e(TAG, "Unable to update focus", e);
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 773e9bf..3090139 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -30,7 +30,6 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.Handler;
-import android.os.IBinder;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.Gravity;
@@ -39,7 +38,6 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewRootImpl;
-import android.view.WindowManagerGlobal;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -72,7 +70,6 @@
     private final ImageView mArrowRight;
     private final ImageView mArrowDown;
     private final ImageView mArrowLeft;
-    private IBinder mFocusGrantToken = null;
 
     private final ViewGroup mScrollView;
     private final ViewGroup mHorizontalScrollView;
@@ -152,10 +149,6 @@
         mListener = listener;
     }
 
-    void setFocusGrantToken(IBinder token) {
-        mFocusGrantToken = token;
-    }
-
     void setExpandedModeEnabled(boolean enabled) {
         mExpandButton.setVisibility(enabled ? VISIBLE : GONE);
     }
@@ -170,8 +163,6 @@
 
     void show(boolean inMoveMode, int gravity) {
         if (DEBUG) Log.d(TAG, "show(), inMoveMode: " + inMoveMode);
-        grantWindowFocus(true);
-
         if (inMoveMode) {
             showMovementHints(gravity);
         } else {
@@ -180,15 +171,11 @@
         animateAlphaTo(1, mMenuFrameView);
     }
 
-    void hide(boolean isInMoveMode) {
+    void hide() {
         if (DEBUG) Log.d(TAG, "hide()");
         animateAlphaTo(0, mActionButtonsContainer);
         animateAlphaTo(0, mMenuFrameView);
         hideMovementHints();
-
-        if (!isInMoveMode) {
-            grantWindowFocus(false);
-        }
     }
 
     private void animateAlphaTo(float alpha, View view) {
@@ -217,17 +204,6 @@
                 || mArrowLeft.getAlpha() != 0f;
     }
 
-    private void grantWindowFocus(boolean grantFocus) {
-        if (DEBUG) Log.d(TAG, "grantWindowFocus(" + grantFocus + ")");
-
-        try {
-            WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
-                    mFocusGrantToken, grantFocus);
-        } catch (Exception e) {
-            Log.e(TAG, "Unable to update focus", e);
-        }
-    }
-
     void setAdditionalActions(List<RemoteAction> actions, Handler mainHandler) {
         if (DEBUG) Log.d(TAG, "setAdditionalActions()");
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 81dacdb..76641f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -979,7 +979,8 @@
             t.setAlpha(dividerLeash, 1);
             t.setLayer(dividerLeash, SPLIT_DIVIDER_LAYER);
             t.setPosition(dividerLeash,
-                    mSplitLayout.getDividerBounds().left, mSplitLayout.getDividerBounds().top);
+                    mSplitLayout.getRefDividerBounds().left,
+                    mSplitLayout.getRefDividerBounds().top);
         } else {
             t.hide(dividerLeash);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 5af1530..4bc5850 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -26,6 +26,7 @@
 import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
+import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE;
 import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_DRAWABLE;
 import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
 import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
@@ -156,9 +157,8 @@
     private BroadcastReceiver mEnterpriseResourceUpdatedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            boolean isDrawable = intent.getBooleanExtra(
-                    EXTRA_RESOURCE_TYPE_DRAWABLE, /* default= */ false);
-            if (!isDrawable) {
+            if (intent.getIntExtra(EXTRA_RESOURCE_TYPE, /* default= */ -1)
+                    != EXTRA_RESOURCE_TYPE_DRAWABLE) {
                 return;
             }
             updateEnterpriseThumbnailDrawable();
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
index 65eb9aa..57bcbc0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
@@ -17,6 +17,7 @@
 package com.android.wm.shell.flicker.apppairs
 
 import android.platform.test.annotations.Presubmit
+import android.view.Display
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -24,6 +25,7 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory
 import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd
 import com.android.wm.shell.flicker.helpers.AppPairsHelper
 import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
@@ -60,7 +62,18 @@
                 // TODO pair apps through normal UX flow
                 executeShellCommand(
                         composePairsCommand(primaryTaskId, nonResizeableTaskId, pair = true))
-                nonResizeableApp?.run { wmHelper.waitForFullScreenApp(nonResizeableApp.component) }
+                val waitConditions = mutableListOf(
+                    WindowManagerConditionsFactory.isWindowVisible(primaryApp.component),
+                    WindowManagerConditionsFactory.isLayerVisible(primaryApp.component),
+                    WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))
+
+                nonResizeableApp?.let {
+                    waitConditions.add(
+                        WindowManagerConditionsFactory.isWindowVisible(nonResizeableApp.component))
+                    waitConditions.add(
+                        WindowManagerConditionsFactory.isLayerVisible(nonResizeableApp.component))
+                }
+                wmHelper.waitFor(*waitConditions.toTypedArray())
             }
         }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
index 0f00ede..cc5b9f9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
@@ -62,7 +62,7 @@
         if (wmHelper == null) {
             device.waitForIdle()
         } else {
-            require(wmHelper.waitImeShown()) { "IME did not appear" }
+            wmHelper.waitImeShown()
         }
     }
 
@@ -79,7 +79,7 @@
             if (wmHelper == null) {
                 uiDevice.waitForIdle()
             } else {
-                require(wmHelper.waitImeGone()) { "IME did did not close" }
+                wmHelper.waitImeGone()
             }
         } else {
             // While pressing the back button should close the IME on TV as well, it may also lead
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 7e232ea..e9d438a 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
@@ -58,17 +58,27 @@
         }
     }
 
-    /** {@inheritDoc}  */
-    override fun launchViaIntent(
+    /**
+     * Launches the app through an intent instead of interacting with the launcher and waits
+     * until the app window is in PIP mode
+     */
+    @JvmOverloads
+    fun launchViaIntentAndWaitForPip(
         wmHelper: WindowManagerStateHelper,
-        expectedWindowName: String,
-        action: String?,
+        expectedWindowName: String = "",
+        action: String? = null,
         stringExtras: Map<String, String>
     ) {
-        super.launchViaIntent(wmHelper, expectedWindowName, action, stringExtras)
-        wmHelper.waitPipShown()
+        launchViaIntentAndWaitShown(wmHelper, expectedWindowName, action, stringExtras,
+            waitConditions = arrayOf(WindowManagerStateHelper.pipShownCondition))
     }
 
+    /**
+     * Expand the PIP window back to full screen via intent and wait until the app is visible
+     */
+    fun exitPipToFullScreenViaIntent(wmHelper: WindowManagerStateHelper) =
+        launchViaIntentAndWaitShown(wmHelper)
+
     private fun focusOnObject(selector: BySelector): Boolean {
         // We expect all the focusable UI elements to be arranged in a way so that it is possible
         // to "cycle" over all them by clicking the D-Pad DOWN button, going back up to "the top"
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 f2c5093..274d34b 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
@@ -64,7 +64,18 @@
      * Defines the transition used to run the test
      */
     override val transition: FlickerBuilder.() -> Unit
-        get() = buildTransition(eachRun = true, stringExtras = emptyMap()) {
+        get() = {
+            setupAndTeardown(this)
+            setup {
+                eachRun {
+                    pipApp.launchViaIntent(wmHelper)
+                }
+            }
+            teardown {
+                eachRun {
+                    pipApp.exit(wmHelper)
+                }
+            }
             transitions {
                 pipApp.clickEnterPipButton(wmHelper)
             }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
index 4f98b70..e2d0834 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
@@ -74,7 +74,7 @@
                 // This will bring PipApp to fullscreen
                 pipApp.expandPipWindowToApp(wmHelper)
                 // Wait until the other app is no longer visible
-                wmHelper.waitForSurfaceAppeared(testApp.component.toWindowName())
+                wmHelper.waitForSurfaceAppeared(testApp.component)
             }
         }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index e00d749..3fe6f02 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -72,9 +72,9 @@
             }
             transitions {
                 // This will bring PipApp to fullscreen
-                pipApp.launchViaIntent(wmHelper)
+                pipApp.exitPipToFullScreenViaIntent(wmHelper)
                 // Wait until the other app is no longer visible
-                wmHelper.waitForSurfaceAppeared(testApp.component.toWindowName())
+                wmHelper.waitForWindowSurfaceDisappeared(testApp.component)
             }
         }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
index bb66f7b..8d542c8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
@@ -56,13 +56,6 @@
                 .sendBroadcast(createIntentWithAction(broadcastAction))
         }
 
-        fun requestOrientationForPip(orientation: Int) {
-            instrumentation.context.sendBroadcast(
-                    createIntentWithAction(Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION)
-                    .putExtra(Components.PipActivity.EXTRA_PIP_ORIENTATION, orientation.toString())
-            )
-        }
-
         companion object {
             // Corresponds to ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
             @JvmStatic
@@ -122,15 +115,14 @@
 
             setup {
                 test {
-                    removeAllTasksButHome()
                     if (!eachRun) {
-                        pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras)
+                        pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
                         wmHelper.waitPipShown()
                     }
                 }
                 eachRun {
                     if (eachRun) {
-                        pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras)
+                        pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
                         wmHelper.waitPipShown()
                     }
                 }
@@ -145,7 +137,6 @@
                     if (!eachRun) {
                         pipApp.exit(wmHelper)
                     }
-                    removeAllTasksButHome()
                 }
             }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index d65388b..f7384e74 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -25,11 +25,14 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
 import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
+import com.android.wm.shell.flicker.testapp.Components
 import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
-import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -39,7 +42,7 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test Pip with orientation changes.
+ * Test exiting Pip with orientation changes.
  * To run this test: `atest WMShellFlickerTests:SetRequestedOrientationWhilePinnedTest`
  */
 @RequiresDevice
@@ -61,30 +64,41 @@
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            setupAndTeardown(this)
-
             setup {
+                test {
+                    removeAllTasksButHome()
+                    device.wakeUpAndGoToHomeScreen()
+                }
                 eachRun {
-                    // Launch the PiP activity fixed as landscape
+                    // Launch the PiP activity fixed as landscape.
                     pipApp.launchViaIntent(wmHelper, stringExtras = mapOf(
-                        EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString(),
-                        EXTRA_ENTER_PIP to "true"))
+                        EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()))
+                    // Enter PiP.
+                    broadcastActionTrigger.doAction(Components.PipActivity.ACTION_ENTER_PIP)
+                    wmHelper.waitPipShown()
+                    wmHelper.waitForRotation(Surface.ROTATION_0)
+                    wmHelper.waitForAppTransitionIdle()
+                    // System bar may fade out during fixed rotation.
+                    wmHelper.waitForNavBarStatusBarVisible()
                 }
             }
             teardown {
                 eachRun {
                     pipApp.exit(wmHelper)
+                    setRotation(Surface.ROTATION_0)
+                }
+                test {
+                    removeAllTasksButHome()
                 }
             }
             transitions {
-                // Request that the orientation is set to landscape
-                broadcastActionTrigger.requestOrientationForPip(ORIENTATION_LANDSCAPE)
-
-                // Launch the activity back into fullscreen and
-                // ensure that it is now in landscape
+                // Launch the activity back into fullscreen and ensure that it is now in landscape
                 pipApp.launchViaIntent(wmHelper)
                 wmHelper.waitForFullScreenApp(pipApp.component)
                 wmHelper.waitForRotation(Surface.ROTATION_90)
+                wmHelper.waitForAppTransitionIdle()
+                // System bar may fade out during fixed rotation.
+                wmHelper.waitForNavBarStatusBarVisible()
             }
         }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTestShellTransit.kt
index 36e2804..8d764a8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTestShellTransit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTestShellTransit.kt
@@ -29,6 +29,10 @@
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
 
+/**
+ * Test exiting Pip with orientation changes.
+ * To run this test: `atest WMShellFlickerTests:SetRequestedOrientationWhilePinnedTestShellTransit`
+ */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index 90f898a..0059846 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -29,6 +29,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayLayout;
 
@@ -72,16 +73,16 @@
     private void initializeMockResources() {
         final TestableResources res = mContext.getOrCreateTestableResources();
         res.addOverride(
-                com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio,
+                R.dimen.config_pictureInPictureDefaultAspectRatio,
                 DEFAULT_ASPECT_RATIO);
         res.addOverride(
-                com.android.internal.R.integer.config_defaultPictureInPictureGravity,
+                R.integer.config_defaultPictureInPictureGravity,
                 Gravity.END | Gravity.BOTTOM);
         res.addOverride(
-                com.android.internal.R.dimen.default_minimal_size_pip_resizable_task,
+                R.dimen.default_minimal_size_pip_resizable_task,
                 DEFAULT_MIN_EDGE_SIZE);
         res.addOverride(
-                com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets,
+                R.string.config_defaultPictureInPictureScreenEdgeInsets,
                 "16x16");
         res.addOverride(
                 com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio,
@@ -107,7 +108,7 @@
     public void onConfigurationChanged_reloadResources() {
         final float newDefaultAspectRatio = (DEFAULT_ASPECT_RATIO + MAX_ASPECT_RATIO) / 2;
         final TestableResources res = mContext.getOrCreateTestableResources();
-        res.addOverride(com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio,
+        res.addOverride(R.dimen.config_pictureInPictureDefaultAspectRatio,
                 newDefaultAspectRatio);
 
         mPipBoundsAlgorithm.onConfigurationChanged(mContext);
@@ -463,7 +464,7 @@
     private void overrideDefaultAspectRatio(float aspectRatio) {
         final TestableResources res = mContext.getOrCreateTestableResources();
         res.addOverride(
-                com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio,
+                R.dimen.config_pictureInPictureDefaultAspectRatio,
                 aspectRatio);
         mPipBoundsAlgorithm.onConfigurationChanged(mContext);
     }
@@ -471,7 +472,7 @@
     private void overrideDefaultStackGravity(int stackGravity) {
         final TestableResources res = mContext.getOrCreateTestableResources();
         res.addOverride(
-                com.android.internal.R.integer.config_defaultPictureInPictureGravity,
+                R.integer.config_defaultPictureInPictureGravity,
                 stackGravity);
         mPipBoundsAlgorithm.onConfigurationChanged(mContext);
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index dda1a82..85527c8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -18,6 +18,7 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
+
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 
@@ -51,6 +52,7 @@
         final SurfaceControl leash = createMockSurface();
         SplitLayout out = mock(SplitLayout.class);
         doReturn(dividerBounds).when(out).getDividerBounds();
+        doReturn(dividerBounds).when(out).getRefDividerBounds();
         doReturn(leash).when(out).getDividerLeash();
         return out;
     }
diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java
index e0f04a1..9f52bf1 100644
--- a/media/java/android/media/ImageWriter.java
+++ b/media/java/android/media/ImageWriter.java
@@ -108,7 +108,6 @@
     private long mUsage = HardwareBuffer.USAGE_CPU_WRITE_OFTEN;
     private @HardwareBuffer.Format int mHardwareBufferFormat;
     private @NamedDataSpace int mDataSpace;
-    private boolean mUseLegacyImageFormat;
 
     // Field below is used by native code, do not access or modify.
     private int mWriterFormat;
@@ -257,7 +256,6 @@
                 + ", maxImages: " + maxImages);
         }
 
-        mUseLegacyImageFormat = useLegacyImageFormat;
         // Note that the underlying BufferQueue is working in synchronous mode
         // to avoid dropping any buffers.
         mNativeContext = nativeInit(new WeakReference<>(this), surface, maxImages, width, height,
@@ -334,12 +332,21 @@
             int hardwareBufferFormat, int dataSpace, int width, int height, long usage) {
         mMaxImages = maxImages;
         mUsage = usage;
-        mHardwareBufferFormat = hardwareBufferFormat;
-        mDataSpace = dataSpace;
-        int publicFormat = PublicFormatUtils.getPublicFormat(hardwareBufferFormat, dataSpace);
+        int imageFormat;
+        // if useSurfaceImageFormatInfo is true, imageFormat will be set to UNKNOWN
+        // and retrieve corresponding hardwareBufferFormat and dataSpace here.
+        if (useSurfaceImageFormatInfo) {
+            imageFormat = ImageFormat.UNKNOWN;
+            mHardwareBufferFormat = PublicFormatUtils.getHalFormat(imageFormat);
+            mDataSpace = PublicFormatUtils.getHalDataspace(imageFormat);
+        } else {
+            imageFormat = PublicFormatUtils.getPublicFormat(hardwareBufferFormat, dataSpace);
+            mHardwareBufferFormat = hardwareBufferFormat;
+            mDataSpace = dataSpace;
+        }
 
         initializeImageWriter(surface, maxImages, useSurfaceImageFormatInfo, false,
-                publicFormat, hardwareBufferFormat, dataSpace, width, height, usage);
+                imageFormat, hardwareBufferFormat, dataSpace, width, height, usage);
     }
 
     /**
@@ -884,27 +891,17 @@
         private @HardwareBuffer.Format int mHardwareBufferFormat = HardwareBuffer.RGBA_8888;
         private @NamedDataSpace int mDataSpace = DataSpace.DATASPACE_UNKNOWN;
         private boolean mUseSurfaceImageFormatInfo = true;
-        // set this as true temporarily now as a workaround to get correct format
-        // when using surface format by default without overriding the image format
-        // in the builder pattern
-        private boolean mUseLegacyImageFormat = true;
+        private boolean mUseLegacyImageFormat = false;
 
         /**
          * Constructs a new builder for {@link ImageWriter}.
          *
-         * <p>Uses {@code surface} input parameter to retrieve image format, hal format
-         * and hal dataspace value for default. </p>
-         *
          * @param surface The destination Surface this writer produces Image data into.
          *
          * @throws IllegalArgumentException if the surface is already abandoned.
          */
         public Builder(@NonNull Surface surface) {
             mSurface = surface;
-            // retrieve format from surface
-            mImageFormat = SurfaceUtils.getSurfaceFormat(surface);
-            mDataSpace = SurfaceUtils.getSurfaceDataspace(surface);
-            mHardwareBufferFormat = PublicFormatUtils.getHalFormat(mImageFormat);
         }
 
         /**
@@ -1058,11 +1055,6 @@
             mWidth = writer.mWidth;
             mHeight = writer.mHeight;
             mDataSpace = writer.mDataSpace;
-
-            if (!mOwner.mUseLegacyImageFormat) {
-                mFormat = PublicFormatUtils.getPublicFormat(
-                    mOwner.mHardwareBufferFormat, mDataSpace);
-            }
         }
 
         @Override
@@ -1083,7 +1075,7 @@
         public int getFormat() {
             throwISEIfImageIsInvalid();
 
-            if (mOwner.mUseLegacyImageFormat && mFormat == -1) {
+            if (mFormat == -1) {
                 mFormat = nativeGetFormat(mDataSpace);
             }
             return mFormat;
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java
index 9175809..f681ba1 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java
@@ -28,6 +28,7 @@
 import android.os.Parcelable;
 import android.os.Process;
 import android.os.SystemClock;
+import android.text.TextUtils;
 import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -500,7 +501,7 @@
                         && roaming == e.roaming && defaultNetwork == e.defaultNetwork
                         && rxBytes == e.rxBytes && rxPackets == e.rxPackets
                         && txBytes == e.txBytes && txPackets == e.txPackets
-                        && operations == e.operations && iface.equals(e.iface);
+                        && operations == e.operations && TextUtils.equals(iface, e.iface);
             }
             return false;
         }
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java
index 72230b4..4117d0f 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java
@@ -177,7 +177,7 @@
                 ret = 0;
                 break;
             case SparseChunk.FILL:
-                ret = mCur.fill[(4 - ((int) mLeft & 0x3)) & 0x3];
+                ret = Byte.toUnsignedInt(mCur.fill[(4 - ((int) mLeft & 0x3)) & 0x3]);
                 break;
             default:
                 throw new IOException("Unsupported Chunk:" + mCur.toString());
diff --git a/packages/SettingsLib/src/com/android/settingslib/devicestate/AndroidSecureSettings.java b/packages/SettingsLib/src/com/android/settingslib/devicestate/AndroidSecureSettings.java
new file mode 100644
index 0000000..8aee576
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/devicestate/AndroidSecureSettings.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.devicestate;
+
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.provider.Settings;
+
+/**
+ * Implementation of {@link SecureSettings} that uses Android's {@link Settings.Secure}
+ * implementation.
+ */
+class AndroidSecureSettings implements SecureSettings {
+
+    private final ContentResolver mContentResolver;
+
+    AndroidSecureSettings(ContentResolver contentResolver) {
+        mContentResolver = contentResolver;
+    }
+
+    @Override
+    public void putStringForUser(String name, String value, int userHandle) {
+        Settings.Secure.putStringForUser(mContentResolver, name, value, userHandle);
+    }
+
+    @Override
+    public String getStringForUser(String name, int userHandle) {
+        return Settings.Secure.getStringForUser(mContentResolver, name, userHandle);
+    }
+
+    @Override
+    public void registerContentObserver(String name, boolean notifyForDescendants,
+            ContentObserver observer, int userHandle) {
+        mContentResolver.registerContentObserver(
+                Settings.Secure.getUriFor(name),
+                notifyForDescendants,
+                observer,
+                userHandle);
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java b/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java
index afd3626..4ed7e19 100644
--- a/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java
@@ -22,8 +22,10 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -33,7 +35,10 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -47,32 +52,44 @@
 
     private static DeviceStateRotationLockSettingsManager sSingleton;
 
-    private final ContentResolver mContentResolver;
-    private final String[] mDeviceStateRotationLockDefaults;
-    private final Handler mMainHandler = Handler.getMain();
+    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
     private final Set<DeviceStateRotationLockSettingsListener> mListeners = new HashSet<>();
+    private final SecureSettings mSecureSettings;
+    private String[] mDeviceStateRotationLockDefaults;
     private SparseIntArray mDeviceStateRotationLockSettings;
     private SparseIntArray mDeviceStateRotationLockFallbackSettings;
+    private String mLastSettingValue;
+    private List<SettableDeviceState> mSettableDeviceStates;
 
-    private DeviceStateRotationLockSettingsManager(Context context) {
-        mContentResolver = context.getContentResolver();
+    @VisibleForTesting
+    DeviceStateRotationLockSettingsManager(Context context, SecureSettings secureSettings) {
+        this.mSecureSettings = secureSettings;
         mDeviceStateRotationLockDefaults =
                 context.getResources()
                         .getStringArray(R.array.config_perDeviceStateRotationLockDefaults);
         loadDefaults();
         initializeInMemoryMap();
-        listenForSettingsChange(context);
+        listenForSettingsChange();
     }
 
     /** Returns a singleton instance of this class */
     public static synchronized DeviceStateRotationLockSettingsManager getInstance(Context context) {
         if (sSingleton == null) {
+            Context applicationContext = context.getApplicationContext();
+            ContentResolver contentResolver = applicationContext.getContentResolver();
+            SecureSettings secureSettings = new AndroidSecureSettings(contentResolver);
             sSingleton =
-                    new DeviceStateRotationLockSettingsManager(context.getApplicationContext());
+                    new DeviceStateRotationLockSettingsManager(applicationContext, secureSettings);
         }
         return sSingleton;
     }
 
+    /** Resets the singleton instance of this class. Only used for testing. */
+    @VisibleForTesting
+    public static synchronized void resetInstance() {
+        sSingleton = null;
+    }
+
     /** Returns true if device-state based rotation lock settings are enabled. */
     public static boolean isDeviceStateRotationLockEnabled(Context context) {
         return context.getResources()
@@ -81,11 +98,11 @@
                 > 0;
     }
 
-    private void listenForSettingsChange(Context context) {
-        context.getContentResolver()
+    private void listenForSettingsChange() {
+        mSecureSettings
                 .registerContentObserver(
-                        Settings.Secure.getUriFor(Settings.Secure.DEVICE_STATE_ROTATION_LOCK),
-                        /* notifyForDescendents= */ false, //NOTYPO
+                        Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+                        /* notifyForDescendants= */ false,
                         new ContentObserver(mMainHandler) {
                             @Override
                             public void onChange(boolean selfChange) {
@@ -180,10 +197,15 @@
         return true;
     }
 
+    /** Returns a list of device states and their respective auto-rotation setting availability. */
+    public List<SettableDeviceState> getSettableDeviceStates() {
+        // Returning a copy to make sure that nothing outside can mutate our internal list.
+        return new ArrayList<>(mSettableDeviceStates);
+    }
+
     private void initializeInMemoryMap() {
         String serializedSetting =
-                Settings.Secure.getStringForUser(
-                        mContentResolver,
+                mSecureSettings.getStringForUser(
                         Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
                         UserHandle.USER_CURRENT);
         if (TextUtils.isEmpty(serializedSetting)) {
@@ -215,6 +237,17 @@
         }
     }
 
+    /**
+     * Resets the state of the class and saved settings back to the default values provided by the
+     * resources config.
+     */
+    @VisibleForTesting
+    public void resetStateForTesting(Resources resources) {
+        mDeviceStateRotationLockDefaults =
+                resources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults);
+        fallbackOnDefaults();
+    }
+
     private void fallbackOnDefaults() {
         loadDefaults();
         persistSettings();
@@ -222,11 +255,7 @@
 
     private void persistSettings() {
         if (mDeviceStateRotationLockSettings.size() == 0) {
-            Settings.Secure.putStringForUser(
-                    mContentResolver,
-                    Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
-                    /* value= */ "",
-                    UserHandle.USER_CURRENT);
+            persistSettingIfChanged(/* newSettingValue= */ "");
             return;
         }
 
@@ -243,14 +272,22 @@
                     .append(SEPARATOR_REGEX)
                     .append(mDeviceStateRotationLockSettings.valueAt(i));
         }
-        Settings.Secure.putStringForUser(
-                mContentResolver,
+        persistSettingIfChanged(stringBuilder.toString());
+    }
+
+    private void persistSettingIfChanged(String newSettingValue) {
+        if (TextUtils.equals(mLastSettingValue, newSettingValue)) {
+            return;
+        }
+        mLastSettingValue = newSettingValue;
+        mSecureSettings.putStringForUser(
                 Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
-                stringBuilder.toString(),
+                /* value= */ newSettingValue,
                 UserHandle.USER_CURRENT);
     }
 
     private void loadDefaults() {
+        mSettableDeviceStates = new ArrayList<>(mDeviceStateRotationLockDefaults.length);
         mDeviceStateRotationLockSettings = new SparseIntArray(
                 mDeviceStateRotationLockDefaults.length);
         mDeviceStateRotationLockFallbackSettings = new SparseIntArray(1);
@@ -271,6 +308,8 @@
                                         + values.length);
                     }
                 }
+                boolean isSettable = rotationLockSetting != DEVICE_STATE_ROTATION_LOCK_IGNORED;
+                mSettableDeviceStates.add(new SettableDeviceState(deviceState, isSettable));
                 mDeviceStateRotationLockSettings.put(deviceState, rotationLockSetting);
             } catch (NumberFormatException e) {
                 Log.wtf(TAG, "Error parsing settings entry. Entry was: " + entry, e);
@@ -300,4 +339,38 @@
         /** Called whenever the settings have changed. */
         void onSettingsChanged();
     }
+
+    /** Represents a device state and whether it has an auto-rotation setting. */
+    public static class SettableDeviceState {
+        private final int mDeviceState;
+        private final boolean mIsSettable;
+
+        SettableDeviceState(int deviceState, boolean isSettable) {
+            mDeviceState = deviceState;
+            mIsSettable = isSettable;
+        }
+
+        /** Returns the device state associated with this object. */
+        public int getDeviceState() {
+            return mDeviceState;
+        }
+
+        /** Returns whether there is an auto-rotation setting for this device state. */
+        public boolean isSettable() {
+            return mIsSettable;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof SettableDeviceState)) return false;
+            SettableDeviceState that = (SettableDeviceState) o;
+            return mDeviceState == that.mDeviceState && mIsSettable == that.mIsSettable;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mDeviceState, mIsSettable);
+        }
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/devicestate/SecureSettings.java b/packages/SettingsLib/src/com/android/settingslib/devicestate/SecureSettings.java
new file mode 100644
index 0000000..1052873
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/devicestate/SecureSettings.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.devicestate;
+
+import android.database.ContentObserver;
+
+/** Minimal wrapper interface around {@link android.provider.Settings.Secure} for easier testing. */
+interface SecureSettings {
+
+    void putStringForUser(String name, String value, int userHandle);
+
+    String getStringForUser(String name, int userHandle);
+
+    void registerContentObserver(String name, boolean notifyForDescendants,
+            ContentObserver settingsObserver, int userHandle);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 7e47f45..9ca431d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -15,13 +15,13 @@
  */
 package com.android.settingslib.media;
 
+import static android.media.MediaRoute2Info.TYPE_BLE_HEADSET;
 import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
 import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
 import static android.media.MediaRoute2Info.TYPE_DOCK;
 import static android.media.MediaRoute2Info.TYPE_GROUP;
 import static android.media.MediaRoute2Info.TYPE_HDMI;
 import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
-import static android.media.MediaRoute2Info.TYPE_BLE_HEADSET;
 import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
 import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
 import static android.media.MediaRoute2Info.TYPE_UNKNOWN;
@@ -163,6 +163,31 @@
         return sessionInfos.get(sessionInfos.size() - 1);
     }
 
+    boolean isRoutingSessionAvailableForVolumeControl() {
+        if (mVolumeAdjustmentForRemoteGroupSessions) {
+            return true;
+        }
+        List<RoutingSessionInfo> sessions =
+                mRouterManager.getRoutingSessions(mPackageName);
+        boolean foundNonSystemSession = false;
+        boolean isGroup = false;
+        for (RoutingSessionInfo session : sessions) {
+            if (!session.isSystemSession()) {
+                foundNonSystemSession = true;
+                int selectedRouteCount = session.getSelectedRoutes().size();
+                if (selectedRouteCount > 1) {
+                    isGroup = true;
+                    break;
+                }
+            }
+        }
+        if (!foundNonSystemSession) {
+            Log.d(TAG, "No routing session for " + mPackageName);
+            return false;
+        }
+        return !isGroup;
+    }
+
     /**
      * Remove a {@code device} from current media.
      *
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 31d5921..5520ea4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -37,9 +37,9 @@
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
 import com.android.settingslib.bluetooth.HearingAidProfile;
+import com.android.settingslib.bluetooth.LeAudioProfile;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
-import com.android.settingslib.bluetooth.LeAudioProfile;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -198,6 +198,14 @@
     }
 
     /**
+     * Returns if the media session is available for volume control.
+     * @return True if this media session is available for colume control, false otherwise.
+     */
+    public boolean isMediaSessionAvailableForVolumeControl() {
+        return mInfoMediaManager.isRoutingSessionAvailableForVolumeControl();
+    }
+
+    /**
      * Start scan connected MediaDevice
      */
     public void startScan() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
index 93be66a..1e1dfae 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
@@ -81,6 +81,7 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        setTheme(R.style.SudThemeGlifV3_DayNight);
         ThemeHelper.trySetDynamicColor(this);
         setContentView(R.layout.avatar_picker);
         setUpButtons();
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
index 4ab6542..9ef6bdf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
@@ -43,6 +43,31 @@
     private static final int INVALID_RSSI = -127;
 
     /**
+     * The intent action shows Wi-Fi dialog to connect Wi-Fi network.
+     * <p>
+     * Input: The calling package should put the chosen
+     * com.android.wifitrackerlib.WifiEntry#getKey() to a string extra in the request bundle into
+     * the {@link #EXTRA_CHOSEN_WIFI_ENTRY_KEY}.
+     * <p>
+     * Output: Nothing.
+     */
+    @VisibleForTesting
+    static final String ACTION_WIFI_DIALOG = "com.android.settings.WIFI_DIALOG";
+
+    /**
+     * Specify a key that indicates the WifiEntry to be configured.
+     */
+    @VisibleForTesting
+    static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key";
+
+    /**
+     * The lookup key for a boolean that indicates whether a chosen WifiEntry request to connect to.
+     * {@code true} means a chosen WifiEntry request to connect to.
+     */
+    @VisibleForTesting
+    static final String EXTRA_CONNECT_FOR_CALLER = "connect_for_caller";
+
+    /**
      * The intent action shows network details settings to allow configuration of Wi-Fi.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
@@ -325,6 +350,19 @@
     }
 
     /**
+     * Returns the Intent for Wi-Fi dialog.
+     *
+     * @param key              The Wi-Fi entry key
+     * @param connectForCaller True if a chosen WifiEntry request to connect to
+     */
+    public static Intent getWifiDialogIntent(String key, boolean connectForCaller) {
+        final Intent intent = new Intent(ACTION_WIFI_DIALOG);
+        intent.putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, key);
+        intent.putExtra(EXTRA_CONNECT_FOR_CALLER, connectForCaller);
+        return intent;
+    }
+
+    /**
      * Returns the Intent for Wi-Fi network details settings.
      *
      * @param key The Wi-Fi entry key
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
new file mode 100644
index 0000000..81006dd
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.devicestate;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.R;
+import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager.SettableDeviceState;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DeviceStateRotationLockSettingsManagerTest {
+
+    @Mock private Context mMockContext;
+    @Mock private Resources mMockResources;
+
+    private DeviceStateRotationLockSettingsManager mManager;
+    private int mNumSettingsChanges = 0;
+    private final ContentObserver mContentObserver = new ContentObserver(null) {
+        @Override
+        public void onChange(boolean selfChange) {
+            mNumSettingsChanges++;
+        }
+    };
+    private final FakeSecureSettings mFakeSecureSettings = new FakeSecureSettings();
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context context = InstrumentationRegistry.getTargetContext();
+        when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
+        when(mMockContext.getResources()).thenReturn(mMockResources);
+        when(mMockContext.getContentResolver()).thenReturn(context.getContentResolver());
+        mFakeSecureSettings.registerContentObserver(
+                Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+                /* notifyForDescendents= */ false, //NOTYPO
+                mContentObserver,
+                UserHandle.USER_CURRENT);
+        mManager = new DeviceStateRotationLockSettingsManager(context, mFakeSecureSettings);
+    }
+
+    @Test
+    public void initialization_settingsAreChangedOnce() {
+        assertThat(mNumSettingsChanges).isEqualTo(1);
+    }
+
+    @Test
+    public void updateSetting_multipleTimes_sameValue_settingsAreChangedOnlyOnce() {
+        mNumSettingsChanges = 0;
+
+        mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true);
+        mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true);
+        mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true);
+
+        assertThat(mNumSettingsChanges).isEqualTo(1);
+    }
+
+    @Test
+    public void updateSetting_multipleTimes_differentValues_settingsAreChangedMultipleTimes() {
+        mNumSettingsChanges = 0;
+
+        mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true);
+        mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ false);
+        mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true);
+
+        assertThat(mNumSettingsChanges).isEqualTo(3);
+    }
+
+    @Test
+    public void getSettableDeviceStates_returnsExpectedValuesInOriginalOrder() {
+        when(mMockResources.getStringArray(
+                R.array.config_perDeviceStateRotationLockDefaults)).thenReturn(
+                new String[]{"2:2", "4:0", "1:1", "0:0"});
+
+        List<SettableDeviceState> settableDeviceStates =
+                DeviceStateRotationLockSettingsManager.getInstance(
+                        mMockContext).getSettableDeviceStates();
+
+        assertThat(settableDeviceStates).containsExactly(
+                new SettableDeviceState(/* deviceState= */ 2, /* isSettable= */ true),
+                new SettableDeviceState(/* deviceState= */ 4, /* isSettable= */ false),
+                new SettableDeviceState(/* deviceState= */ 1, /* isSettable= */ true),
+                new SettableDeviceState(/* deviceState= */ 0, /* isSettable= */ false)
+        ).inOrder();
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/FakeSecureSettings.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/FakeSecureSettings.java
new file mode 100644
index 0000000..91baa68
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/FakeSecureSettings.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.devicestate;
+
+import android.database.ContentObserver;
+import android.util.Pair;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/** Fake implementation of {@link SecureSettings} that stores everything in memory. */
+class FakeSecureSettings implements SecureSettings {
+
+    private final Map<SettingsKey, String> mValues = new HashMap<>();
+    private final Multimap<SettingsKey, ContentObserver> mContentObservers = HashMultimap.create();
+
+    @Override
+    public void putStringForUser(String name, String value, int userHandle) {
+        SettingsKey settingsKey = new SettingsKey(userHandle, name);
+        mValues.put(settingsKey, value);
+        for (ContentObserver observer : mContentObservers.get(settingsKey)) {
+            observer.onChange(/* selfChange= */ false);
+        }
+    }
+
+    @Override
+    public String getStringForUser(String name, int userHandle) {
+        return mValues.getOrDefault(new SettingsKey(userHandle, name), "");
+    }
+
+    @Override
+    public void registerContentObserver(String name, boolean notifyForDescendants,
+            ContentObserver settingsObserver, int userHandle) {
+        mContentObservers.put(new SettingsKey(userHandle, name), settingsObserver);
+    }
+
+    private static class SettingsKey extends Pair<Integer, String> {
+
+        SettingsKey(Integer userHandle, String settingName) {
+            super(userHandle, settingName);
+        }
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/OWNERS b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/OWNERS
new file mode 100644
index 0000000..98f4123
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/OWNERS
@@ -0,0 +1,3 @@
+# Default reviewers for this and subdirectories.
+alexflo@google.com
+chrisgollner@google.com
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java
index 06b6fc8..b2258e1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java
@@ -80,8 +80,8 @@
         assertEquals(Settings.ACTION_MANAGE_SUPERVISOR_RESTRICTED_SETTING,
                 intentCaptor.getValue().getAction());
         assertEquals(Settings.SUPERVISOR_VERIFICATION_SETTING_BIOMETRICS,
-                intentCaptor.getValue().getStringExtra(
-                        Settings.EXTRA_SUPERVISOR_RESTRICTED_SETTING_KEY));
+                intentCaptor.getValue().getIntExtra(
+                        Settings.EXTRA_SUPERVISOR_RESTRICTED_SETTING_KEY, -1));
         assertEquals(componentName.getPackageName(), intentCaptor.getValue().getPackage());
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
index e7b3fe9..6956105 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
@@ -156,6 +156,27 @@
     }
 
     @Test
+    public void getWifiDialogIntent_returnsCorrectValues() {
+        String key = "test_key";
+
+        // Test that connectForCaller is true.
+        Intent intent = WifiUtils.getWifiDialogIntent(key, true /* connectForCaller */);
+
+        assertThat(intent.getAction()).isEqualTo(WifiUtils.ACTION_WIFI_DIALOG);
+        assertThat(intent.getStringExtra(WifiUtils.EXTRA_CHOSEN_WIFI_ENTRY_KEY)).isEqualTo(key);
+        assertThat(intent.getBooleanExtra(WifiUtils.EXTRA_CONNECT_FOR_CALLER, true))
+                .isEqualTo(true /* connectForCaller */);
+
+        // Test that connectForCaller is false.
+        intent = WifiUtils.getWifiDialogIntent(key, false /* connectForCaller */);
+
+        assertThat(intent.getAction()).isEqualTo(WifiUtils.ACTION_WIFI_DIALOG);
+        assertThat(intent.getStringExtra(WifiUtils.EXTRA_CHOSEN_WIFI_ENTRY_KEY)).isEqualTo(key);
+        assertThat(intent.getBooleanExtra(WifiUtils.EXTRA_CONNECT_FOR_CALLER, true))
+                .isEqualTo(false /* connectForCaller */);
+    }
+
+    @Test
     public void getWifiDetailsSettingsIntent_returnsCorrectValues() {
         final String key = "test_key";
 
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 8e35ee96..a36a449 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -193,6 +193,7 @@
         Settings.Secure.NOTIFICATION_BUBBLES,
         Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED,
         Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
+        Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
         Settings.Secure.LOCKSCREEN_SHOW_WALLET,
         Settings.Secure.LOCK_SCREEN_SHOW_QR_CODE_SCANNER,
         Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 5f549fd..fc2c2339 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -152,6 +152,7 @@
         VALIDATORS.put(Secure.CONTROLS_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.POWER_MENU_LOCKED_SHOW_CONTENT, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.LOCKSCREEN_SHOW_CONTROLS, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.LOCKSCREEN_SHOW_WALLET, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.LOCK_SCREEN_SHOW_QR_CODE_SCANNER, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, BOOLEAN_VALIDATOR);
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 121f9e5..070aa1b 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -110,7 +110,7 @@
     <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
     <uses-permission android:name="android.permission.READ_INSTALL_SESSIONS" />
     <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
-    <uses-permission android:name="android.permission.SEND_LOST_MODE_LOCATION_UPDATES" />
+    <uses-permission android:name="android.permission.TRIGGER_LOST_MODE" />
     <!-- ACCESS_BACKGROUND_LOCATION is needed for testing purposes only. -->
     <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
     <!-- ACCESS_MTP is needed for testing purposes only. -->
@@ -593,6 +593,9 @@
 
     <uses-permission android:name="android.permission.MANAGE_APP_HIBERNATION"/>
 
+    <!-- Permission required for CTS test - MediaCodecResourceTest -->
+    <uses-permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID" />
+
     <!-- Permission required for CTS test - ResourceObserverNativeTest -->
     <uses-permission android:name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER" />
 
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index ad6074a..4b1d00b 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -311,6 +311,9 @@
     <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
     <uses-permission android:name="android.permission.SUPPRESS_CLIPBOARD_ACCESS_NOTIFICATION" />
 
+    <!-- To change system captions state -->
+    <uses-permission android:name="android.permission.SET_SYSTEM_AUDIO_CAPTION" />
+
     <protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
     <protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
     <protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
@@ -767,22 +770,6 @@
             </intent-filter>
         </activity>
 
-        <activity android:name=".chooser.ChooserActivity"
-                android:theme="@*android:style/Theme.NoDisplay"
-                android:finishOnCloseSystemDialogs="true"
-                android:excludeFromRecents="true"
-                android:documentLaunchMode="never"
-                android:relinquishTaskIdentity="true"
-                android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
-                android:process=":ui"
-                android:visibleToInstantApps="true"
-                android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.CHOOSER" />
-                <category android:name="android.intent.category.VOICE" />
-            </intent-filter>
-        </activity>
-
         <activity android:name=".clipboardoverlay.EditTextActivity"
                   android:theme="@style/EditTextActivity"
                   android:exported="false"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index c3f6a5d..0da60f0 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -298,6 +298,9 @@
              *
              * Important: The view must be attached to a [ViewGroup] when calling this function and
              * during the animation. For safety, this method will return null when it is not.
+             *
+             * Note: The background of [view] should be a (rounded) rectangle so that it can be
+             * properly animated.
              */
             @JvmStatic
             fun fromView(view: View, cujType: Int? = null): Controller? {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index a3c5649..50178f4 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -79,6 +79,9 @@
      * If [animateBackgroundBoundsChange] is true, then the background of the dialog will be
      * animated when the dialog bounds change.
      *
+     * Note: The background of [view] should be a (rounded) rectangle so that it can be properly
+     * animated.
+     *
      * Caveats: When calling this function and [dialog] is not a fullscreen dialog, then it will be
      * made fullscreen and 2 views will be inserted between the dialog DecorView and its children.
      */
@@ -153,6 +156,9 @@
      * activity started, when the dialog to app animation is done (or when it is cancelled). If this
      * method returns null, then the dialog won't be dismissed.
      *
+     * Note: The background of [view] should be a (rounded) rectangle so that it can be properly
+     * animated.
+     *
      * @param view any view inside the dialog to animate.
      */
     @JvmOverloads
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
index 0a0530c0..3d2f570 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
@@ -60,7 +60,6 @@
 
     boolean areCaptionsEnabled();
     void setCaptionsEnabled(boolean isEnabled);
-    boolean isCaptionStreamOptedOut();
 
     void getCaptionsComponentState(boolean fromTooltip);
 
diff --git a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
index 59f87da..9d801d2 100644
--- a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
+++ b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
@@ -28,7 +28,7 @@
         android:layout_height="match_parent"
         android:layout_marginEnd="@dimen/new_qs_footer_action_inset"
         android:background="@drawable/qs_security_footer_background"
-        android:layout_gravity="center"
+        android:layout_gravity="end"
         android:gravity="center"
         android:paddingHorizontal="@dimen/qs_footer_padding"
         >
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index dad4c19..b98f413 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -123,6 +123,7 @@
     <dimen name="bouncer_user_switcher_icon_size">190dp</dimen>
     <dimen name="bouncer_user_switcher_icon_size_plus_margin">222dp</dimen>
 
+    <dimen name="user_switcher_fullscreen_horizontal_gap">64dp</dimen>
     <dimen name="user_switcher_icon_selected_width">8dp</dimen>
     <dimen name="user_switcher_fullscreen_button_text_size">14sp</dimen>
     <dimen name="user_switcher_fullscreen_button_padding">12dp</dimen>
diff --git a/packages/SystemUI/res/color/caption_tint_color_selector.xml b/packages/SystemUI/res/color/caption_tint_color_selector.xml
index 30843ec..5239d26 100644
--- a/packages/SystemUI/res/color/caption_tint_color_selector.xml
+++ b/packages/SystemUI/res/color/caption_tint_color_selector.xml
@@ -16,8 +16,5 @@
   -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android"
           xmlns:sysui="http://schemas.android.com/apk/res-auto">
-    <item sysui:optedOut="true"
-          android:color="?android:attr/colorButtonNormal"/>
-
     <item android:color="?android:attr/colorAccent"/>
 </selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/notif_dungeon_bg_gradient.xml b/packages/SystemUI/res/drawable/notif_dungeon_bg_gradient.xml
deleted file mode 100644
index e456e29..0000000
--- a/packages/SystemUI/res/drawable/notif_dungeon_bg_gradient.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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.
-  -->
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle">
-    <gradient
-        android:angle="90"
-        android:startColor="#ff000000"
-        android:endColor="#00000000"
-        android:type="linear" />
-</shape>
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
index d057f5f..31a8c3b 100644
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
@@ -19,13 +19,17 @@
     <ripple
         android:color="?android:attr/colorControlHighlight">
         <item android:id="@android:id/mask">
-            <shape android:shape="oval">
+            <!-- We make this shape a rounded rectangle instead of a oval so that it can animate -->
+            <!-- properly into an app/dialog. -->
+            <shape android:shape="rectangle">
                 <solid android:color="@android:color/white"/>
+                <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
             </shape>
         </item>
         <item>
-            <shape android:shape="oval">
+            <shape android:shape="rectangle">
                 <solid android:color="?attr/offStateColor"/>
+                <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
             </shape>
         </item>
 
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
index 944061c..021a85f 100644
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
@@ -19,13 +19,17 @@
     <ripple
         android:color="?android:attr/colorControlHighlight">
         <item android:id="@android:id/mask">
-            <shape android:shape="oval">
+            <!-- We make this shape a rounded rectangle instead of a oval so that it can animate -->
+            <!-- properly into an app/dialog. -->
+            <shape android:shape="rectangle">
                 <solid android:color="@android:color/white"/>
+                <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
             </shape>
         </item>
         <item>
-            <shape android:shape="oval">
+            <shape android:shape="rectangle">
                 <solid android:color="?android:attr/colorAccent"/>
+                <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
             </shape>
         </item>
 
diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog.xml b/packages/SystemUI/res/layout-land-television/volume_dialog.xml
index 6b5629f..0fbc519 100644
--- a/packages/SystemUI/res/layout-land-television/volume_dialog.xml
+++ b/packages/SystemUI/res/layout-land-television/volume_dialog.xml
@@ -73,8 +73,7 @@
                 android:layout_height="match_parent"
                 android:tint="@color/caption_tint_color_selector"
                 android:layout_gravity="center"
-                android:soundEffectsEnabled="false"
-                sysui:optedOut="false"/>
+                android:soundEffectsEnabled="false"/>
 
         </FrameLayout>
 
diff --git a/packages/SystemUI/res/layout-land/volume_dialog.xml b/packages/SystemUI/res/layout-land/volume_dialog.xml
index f1cda27..3b70dc0 100644
--- a/packages/SystemUI/res/layout-land/volume_dialog.xml
+++ b/packages/SystemUI/res/layout-land/volume_dialog.xml
@@ -136,8 +136,7 @@
                 android:layout_height="match_parent"
                 android:tint="?android:attr/colorAccent"
                 android:layout_gravity="center"
-                android:soundEffectsEnabled="false"
-                sysui:optedOut="false"/>
+                android:soundEffectsEnabled="false" />
         </FrameLayout>
     </LinearLayout>
 
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_preview.xml b/packages/SystemUI/res/layout/dream_overlay_complication_preview.xml
new file mode 100644
index 0000000..37b8365
--- /dev/null
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_preview.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/dream_preview_text"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:textSize="@dimen/dream_overlay_complication_preview_text_size"
+    android:textColor="@android:color/white"
+    android:shadowColor="@color/keyguard_shadow_color"
+    android:shadowRadius="?attr/shadowRadius"
+    android:gravity="center_vertical"
+    android:drawableStart="@drawable/ic_arrow_back"
+    android:drawablePadding="@dimen/dream_overlay_complication_preview_icon_padding"
+    android:drawableTint="@android:color/white"/>
diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml
index 330f515..8e83b4a 100644
--- a/packages/SystemUI/res/layout/dream_overlay_container.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_container.xml
@@ -34,34 +34,5 @@
         app:layout_constraintBottom_toBottomOf="parent"
         />
 
-    <com.android.systemui.dreams.DreamOverlayStatusBarView
-        android:id="@+id/dream_overlay_status_bar"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/dream_overlay_status_bar_height"
-        android:paddingEnd="@dimen/dream_overlay_status_bar_margin"
-        android:paddingStart="@dimen/dream_overlay_status_bar_margin"
-        app:layout_constraintTop_toTopOf="parent">
-
-        <androidx.constraintlayout.widget.ConstraintLayout
-            android:id="@+id/dream_overlay_system_status"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            app:layout_constraintEnd_toEndOf="parent">
-
-            <com.android.systemui.statusbar.AlphaOptimizedImageView
-                android:id="@+id/dream_overlay_wifi_status"
-                android:layout_width="@dimen/status_bar_wifi_signal_size"
-                android:layout_height="match_parent"
-                android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
-                android:visibility="gone"
-                app:layout_constraintEnd_toStartOf="@id/dream_overlay_battery" />
-
-            <com.android.systemui.battery.BatteryMeterView
-                android:id="@+id/dream_overlay_battery"
-                android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                app:layout_constraintEnd_toEndOf="parent" />
-
-        </androidx.constraintlayout.widget.ConstraintLayout>
-    </com.android.systemui.dreams.DreamOverlayStatusBarView>
+    <include layout="@layout/dream_overlay_status_bar_view" />
 </com.android.systemui.dreams.DreamOverlayContainerView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
new file mode 100644
index 0000000..813787e
--- /dev/null
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<com.android.systemui.dreams.DreamOverlayStatusBarView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/dream_overlay_status_bar"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/dream_overlay_status_bar_height"
+    android:paddingEnd="@dimen/dream_overlay_status_bar_margin"
+    android:paddingStart="@dimen/dream_overlay_status_bar_margin"
+    app:layout_constraintTop_toTopOf="parent">
+
+    <com.android.systemui.dreams.DreamOverlayDotImageView
+        android:id="@+id/dream_overlay_notification_indicator"
+        android:layout_width="@dimen/dream_overlay_notification_indicator_size"
+        android:layout_height="@dimen/dream_overlay_notification_indicator_size"
+        android:visibility="gone"
+        android:contentDescription="@string/dream_overlay_status_bar_notification_indicator"
+        app:dotColor="@android:color/white"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent" />
+
+    <LinearLayout
+        android:id="@+id/dream_overlay_system_status"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:orientation="horizontal"
+        app:layout_constraintEnd_toEndOf="parent">
+
+        <com.android.systemui.statusbar.AlphaOptimizedImageView
+            android:id="@+id/dream_overlay_assistant_guest_mode_enabled"
+            android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
+            android:layout_height="match_parent"
+            android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
+            android:src="@drawable/ic_account_circle"
+            android:tint="@android:color/white"
+            android:visibility="gone"
+            android:contentDescription=
+                "@string/dream_overlay_status_bar_assistant_guest_mode_enabled" />
+
+        <com.android.systemui.statusbar.AlphaOptimizedImageView
+            android:id="@+id/dream_overlay_alarm_set"
+            android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
+            android:layout_height="match_parent"
+            android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
+            android:src="@drawable/ic_alarm"
+            android:tint="@android:color/white"
+            android:visibility="gone"
+            android:contentDescription="@string/dream_overlay_status_bar_alarm_set" />
+
+        <com.android.systemui.statusbar.AlphaOptimizedImageView
+            android:id="@+id/dream_overlay_priority_mode"
+            android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
+            android:layout_height="match_parent"
+            android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
+            android:src="@drawable/ic_remove_circle"
+            android:tint="@android:color/white"
+            android:visibility="gone"
+            android:contentDescription="@string/dream_overlay_status_bar_priority_mode" />
+
+        <com.android.systemui.statusbar.AlphaOptimizedImageView
+            android:id="@+id/dream_overlay_wifi_status"
+            android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
+            android:layout_height="match_parent"
+            android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
+            android:src="@drawable/ic_signal_wifi_off"
+            android:visibility="gone"
+            android:contentDescription="@string/dream_overlay_status_bar_wifi_off" />
+
+        <com.android.systemui.dreams.DreamOverlayDotImageView
+            android:id="@+id/dream_overlay_camera_mic_off"
+            android:layout_width="@dimen/dream_overlay_camera_mic_off_indicator_size"
+            android:layout_height="@dimen/dream_overlay_camera_mic_off_indicator_size"
+            android:layout_gravity="center_vertical"
+            android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
+            android:visibility="gone"
+            android:contentDescription="@string/dream_overlay_status_bar_camera_mic_off"
+            app:dotColor="@color/dream_overlay_camera_mic_off_dot_color" />
+
+    </LinearLayout>
+</com.android.systemui.dreams.DreamOverlayStatusBarView>
diff --git a/packages/SystemUI/res/layout/foreground_service_dungeon.xml b/packages/SystemUI/res/layout/foreground_service_dungeon.xml
deleted file mode 100644
index d4e98e2..0000000
--- a/packages/SystemUI/res/layout/foreground_service_dungeon.xml
+++ /dev/null
@@ -1,61 +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.
-  -->
-
-<com.android.systemui.statusbar.notification.row.ForegroundServiceDungeonView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/foreground_service_dungeon"
-    android:layout_width="@dimen/qs_panel_width"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center_horizontal|bottom"
-    android:visibility="visible"
->
-    <LinearLayout
-        android:layout_height="wrap_content"
-        android:layout_width="match_parent"
-        android:orientation="vertical"
-        android:gravity="bottom"
-        android:visibility="visible"
-        android:background="@drawable/notif_dungeon_bg_gradient"
-    >
-
-        <!-- divider view -->
-        <View
-            android:layout_width="match_parent"
-            android:layout_height="1dp"
-            android:background="@color/GM2_grey_200"
-            android:visibility="visible"
-        />
-
-        <TextView
-            android:id="@+id/dungeon_title"
-            android:layout_height="48dp"
-            android:layout_width="match_parent"
-            android:padding="8dp"
-            android:text="Apps active in background"
-            android:textColor="@color/GM2_grey_200"
-        />
-
-        <!--  List containing the actual foreground service notifications  -->
-        <LinearLayout
-            android:id="@+id/entry_list"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="bottom"
-            android:orientation="vertical" >
-        </LinearLayout>
-
-    </LinearLayout>
-</com.android.systemui.statusbar.notification.row.ForegroundServiceDungeonView>
diff --git a/packages/SystemUI/res/layout/foreground_service_dungeon_row.xml b/packages/SystemUI/res/layout/foreground_service_dungeon_row.xml
deleted file mode 100644
index a6f1638..0000000
--- a/packages/SystemUI/res/layout/foreground_service_dungeon_row.xml
+++ /dev/null
@@ -1,43 +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.
-  -->
-
-<com.android.systemui.statusbar.notification.row.DungeonRow
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/foreground_service_dungeon_row"
-    android:layout_width="match_parent"
-    android:layout_height="48dp"
-    android:padding="8dp"
-    android:clickable="true"
-    android:orientation="horizontal" >
-
-    <com.android.systemui.statusbar.StatusBarIconView
-        android:id="@+id/icon"
-        android:layout_width="24dp"
-        android:layout_height="24dp"
-        android:padding="4dp" />
-
-    <TextView
-        android:id="@+id/app_name"
-        android:layout_width="0dp"
-        android:layout_weight="1"
-        android:layout_height="wrap_content"
-        android:paddingStart="4dp"
-        android:gravity="center_vertical"
-        android:layout_gravity="center_vertical"
-        android:textColor="@color/GM2_grey_200"
-    />
-
-</com.android.systemui.statusbar.notification.row.DungeonRow>
diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
index 2d883bc..6bb6c2d 100644
--- a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
+++ b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
@@ -21,9 +21,8 @@
     android:id="@+id/user_switcher_root"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:layout_marginBottom="40dp"
-    android:layout_marginEnd="60dp"
-    android:layout_marginStart="60dp">
+    android:layout_marginVertical="40dp"
+    android:layout_marginHorizontal="60dp">
 
   <androidx.constraintlayout.helper.widget.Flow
       android:id="@+id/flow"
@@ -36,7 +35,7 @@
       app:flow_horizontalBias="0.5"
       app:flow_verticalAlign="center"
       app:flow_wrapMode="chain"
-      app:flow_horizontalGap="64dp"
+      app:flow_horizontalGap="@dimen/user_switcher_fullscreen_horizontal_gap"
       app:flow_verticalGap="44dp"
       app:flow_horizontalStyle="packed"/>
 
diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen_item.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen_item.xml
index 3319442..a3d9a69 100644
--- a/packages/SystemUI/res/layout/user_switcher_fullscreen_item.xml
+++ b/packages/SystemUI/res/layout/user_switcher_fullscreen_item.xml
@@ -21,8 +21,8 @@
   <ImageView
       android:id="@+id/user_switcher_icon"
       android:layout_gravity="center"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content" />
+      android:layout_width="@dimen/bouncer_user_switcher_icon_size_plus_margin"
+      android:layout_height="@dimen/bouncer_user_switcher_icon_size_plus_margin" />
   <TextView
       style="@style/Bouncer.UserSwitcher.Spinner.Item"
       android:id="@+id/user_switcher_text"
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 51718d9..6a192d4 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -135,8 +135,7 @@
                 android:layout_height="match_parent"
                 android:tint="?android:attr/colorAccent"
                 android:layout_gravity="center"
-                android:soundEffectsEnabled="false"
-                sysui:optedOut="false"/>
+                android:soundEffectsEnabled="false"/>
         </FrameLayout>
     </LinearLayout>
 
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index c37c804..8a4516a 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -42,4 +42,8 @@
          the shade (in alpha) -->
     <dimen name="lockscreen_shade_scrim_transition_distance">200dp</dimen>
 
+    <!-- Distance that the full shade transition takes in order for media to fully transition to
+     the shade -->
+    <dimen name="lockscreen_shade_media_transition_distance">200dp</dimen>
+
 </resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 2992859..62903d5 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -154,10 +154,6 @@
         <attr name="showAirplaneMode" format="boolean" />
     </declare-styleable>
 
-    <declare-styleable name="CaptionsToggleImageButton">
-        <attr name="optedOut" format="boolean" />
-    </declare-styleable>
-
     <declare-styleable name="IlluminationDrawable">
         <attr name="highlight" format="integer" />
         <attr name="cornerRadius" format="dimension" />
@@ -199,5 +195,9 @@
     </declare-styleable>
 
     <attr name="overlayButtonTextColor" format="color" />
+
+    <declare-styleable name="DreamOverlayDotImageView">
+        <attr name="dotColor" format="color" />
+    </declare-styleable>
 </resources>
 
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index f4e7cf3..dc74700 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -225,4 +225,6 @@
     <color name="settingslib_track_off_color">@color/settingslib_track_off</color>
     <color name="connected_network_primary_color">#191C18</color>
     <color name="connected_network_secondary_color">#41493D</color>
+
+    <color name="dream_overlay_camera_mic_off_dot_color">#FCBE03</color>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 3704134..debf95b 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1332,17 +1332,23 @@
     <dimen name="fgs_manager_min_width_minor">100%</dimen>
 
     <!-- Dream overlay related dimensions -->
-    <dimen name="dream_overlay_status_bar_height">80dp</dimen>
+    <dimen name="dream_overlay_status_bar_height">60dp</dimen>
     <dimen name="dream_overlay_status_bar_margin">40dp</dimen>
     <dimen name="dream_overlay_status_icon_margin">8dp</dimen>
+    <dimen name="dream_overlay_status_bar_icon_size">
+        @*android:dimen/status_bar_system_icon_size</dimen>
     <!-- Height of the area at the top of the dream overlay to allow dragging down the notifications
          shade. -->
     <dimen name="dream_overlay_notifications_drag_area_height">100dp</dimen>
+    <dimen name="dream_overlay_camera_mic_off_indicator_size">8dp</dimen>
+    <dimen name="dream_overlay_notification_indicator_size">6dp</dimen>
 
     <!-- Dream overlay complications related dimensions -->
     <dimen name="dream_overlay_complication_clock_time_text_size">72sp</dimen>
     <dimen name="dream_overlay_complication_clock_date_text_size">18sp</dimen>
     <dimen name="dream_overlay_complication_weather_text_size">18sp</dimen>
+    <dimen name="dream_overlay_complication_preview_text_size">36sp</dimen>
+    <dimen name="dream_overlay_complication_preview_icon_padding">28dp</dimen>
 
     <!-- The position of the end guide, which dream overlay complications can align their start with
          if their end is aligned with the parent end. Represented as the percentage over from the
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 23b2529..df16b0d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2410,4 +2410,17 @@
 
     <!-- Toast shown when a notification does not support dragging to split [CHAR LIMIT=NONE] -->
     <string name="drag_split_not_supported">This notification does not support dragging to Splitscreen.</string>
+
+    <!-- Content description for the Wi-Fi off icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
+    <string name="dream_overlay_status_bar_wifi_off">Wi\u2011Fi unavailable</string>
+    <!-- Content description for the priority mode icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
+    <string name="dream_overlay_status_bar_priority_mode">Priority mode</string>
+    <!-- Content description for the alarm set icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
+    <string name="dream_overlay_status_bar_alarm_set">Alarm set</string>
+    <!-- Content description for the assistant guest mode enabled icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
+    <string name="dream_overlay_status_bar_assistant_guest_mode_enabled">Assistant guest mode enabled</string>
+    <!-- Content description for the camera and mic off icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
+    <string name="dream_overlay_status_bar_camera_mic_off">Camera and mic are off</string>
+    <!-- Content description for the camera and mic off icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
+    <string name="dream_overlay_status_bar_notification_indicator">There are notifications</string>
 </resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
index b611c96..6ad9161 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
@@ -35,6 +35,11 @@
     val resourceId: Int
 }
 
+interface SysPropFlag<T> : Flag<T> {
+    val name: String
+    val default: T
+}
+
 // Consider using the "parcelize" kotlin library.
 
 data class BooleanFlag @JvmOverloads constructor(
@@ -66,6 +71,12 @@
     @BoolRes override val resourceId: Int
 ) : ResourceFlag<Boolean>
 
+data class SysPropBooleanFlag constructor(
+    override val id: Int,
+    override val name: String,
+    override val default: Boolean = false
+) : SysPropFlag<Boolean>
+
 data class StringFlag @JvmOverloads constructor(
     override val id: Int,
     override val default: String = ""
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
index ec619dd..149f6e8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
@@ -54,7 +54,7 @@
      * An action called on restart which takes as an argument whether the listeners requested
      * that the restart be suppressed
      */
-    var restartAction: Consumer<Boolean>? = null
+    var onSettingsChangedAction: Consumer<Boolean>? = null
     var clearCacheAction: Consumer<Int>? = null
     private val listeners: MutableSet<PerFlagListener> = mutableSetOf()
     private val settingsObserver: ContentObserver = SettingsObserver()
@@ -154,11 +154,11 @@
             val idStr = parts[parts.size - 1]
             val id = try { idStr.toInt() } catch (e: NumberFormatException) { return }
             clearCacheAction?.accept(id)
-            dispatchListenersAndMaybeRestart(id)
+            dispatchListenersAndMaybeRestart(id, onSettingsChangedAction)
         }
     }
 
-    fun dispatchListenersAndMaybeRestart(id: Int) {
+    fun dispatchListenersAndMaybeRestart(id: Int, restartAction: Consumer<Boolean>?) {
         val filteredListeners: List<FlagListenable.Listener> = synchronized(listeners) {
             listeners.mapNotNull { if (it.id == id) it.listener else null }
         }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 08b4d3f..2b1c47f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -103,8 +103,8 @@
     // enabled (since it's used to navigate back within the bubbled app, or to collapse the bubble
     // stack.
     public static final int SYSUI_STATE_BUBBLES_EXPANDED = 1 << 14;
-    // The global actions dialog is showing
-    public static final int SYSUI_STATE_GLOBAL_ACTIONS_SHOWING = 1 << 15;
+    // A SysUI dialog is showing.
+    public static final int SYSUI_STATE_DIALOG_SHOWING = 1 << 15;
     // The one-handed mode is active
     public static final int SYSUI_STATE_ONE_HANDED_ACTIVE = 1 << 16;
     // Allow system gesture no matter the system bar(s) is visible or not
@@ -140,7 +140,7 @@
             SYSUI_STATE_TRACING_ENABLED,
             SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED,
             SYSUI_STATE_BUBBLES_EXPANDED,
-            SYSUI_STATE_GLOBAL_ACTIONS_SHOWING,
+            SYSUI_STATE_DIALOG_SHOWING,
             SYSUI_STATE_ONE_HANDED_ACTIVE,
             SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY,
             SYSUI_STATE_IME_SHOWING,
@@ -166,7 +166,7 @@
         str.add((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0
                 ? "keygrd_occluded" : "");
         str.add((flags & SYSUI_STATE_BOUNCER_SHOWING) != 0 ? "bouncer_visible" : "");
-        str.add((flags & SYSUI_STATE_GLOBAL_ACTIONS_SHOWING) != 0 ? "global_actions" : "");
+        str.add((flags & SYSUI_STATE_DIALOG_SHOWING) != 0 ? "dialog_showing" : "");
         str.add((flags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0 ? "a11y_click" : "");
         str.add((flags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0 ? "a11y_long_click" : "");
         str.add((flags & SYSUI_STATE_TRACING_ENABLED) != 0 ? "tracing" : "");
@@ -256,7 +256,7 @@
     public static boolean isBackGestureDisabled(int sysuiStateFlags) {
         // Always allow when the bouncer/global actions is showing (even on top of the keyguard)
         if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0
-                || (sysuiStateFlags & SYSUI_STATE_GLOBAL_ACTIONS_SHOWING) != 0) {
+                || (sysuiStateFlags & SYSUI_STATE_DIALOG_SHOWING) != 0) {
             return false;
         }
         if ((sysuiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index b32c2b6..84b6ace 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -38,6 +38,7 @@
 import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
 import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
 import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
+import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.appops.AppOpsController;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -107,6 +108,7 @@
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.policy.AccessibilityController;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -372,6 +374,8 @@
     @Inject Lazy<AmbientState> mAmbientStateLazy;
     @Inject Lazy<GroupMembershipManager> mGroupMembershipManagerLazy;
     @Inject Lazy<GroupExpansionManager> mGroupExpansionManagerLazy;
+    @Inject Lazy<SystemUIDialogManager> mSystemUIDialogManagerLazy;
+    @Inject Lazy<DialogLaunchAnimator> mDialogLaunchAnimatorLazy;
 
     @Inject
     public Dependency() {
@@ -592,6 +596,8 @@
         mProviders.put(AmbientState.class, mAmbientStateLazy::get);
         mProviders.put(GroupMembershipManager.class, mGroupMembershipManagerLazy::get);
         mProviders.put(GroupExpansionManager.class, mGroupExpansionManagerLazy::get);
+        mProviders.put(SystemUIDialogManager.class, mSystemUIDialogManagerLazy::get);
+        mProviders.put(DialogLaunchAnimator.class, mDialogLaunchAnimatorLazy::get);
 
         Dependency.setInstance(this);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
index 6bec8aa..56046d9 100644
--- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
+++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
@@ -49,7 +49,7 @@
     private val shouldDrawCutout: Boolean = DisplayCutout.getFillBuiltInDisplayCutout(
             context.resources, context.display?.uniqueId)
     private var displayMode: Display.Mode? = null
-    private val location = IntArray(2)
+    protected val location = IntArray(2)
     protected var displayRotation = 0
 
     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
@@ -65,7 +65,8 @@
     @JvmField val protectionPath: Path = Path()
     private val protectionRectOrig: RectF = RectF()
     private val protectionPathOrig: Path = Path()
-    private var cameraProtectionProgress: Float = HIDDEN_CAMERA_PROTECTION_SCALE
+    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+    var cameraProtectionProgress: Float = HIDDEN_CAMERA_PROTECTION_SCALE
     private var cameraProtectionAnimator: ValueAnimator? = null
 
     constructor(context: Context) : super(context)
@@ -273,4 +274,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
index ee1d9a3..4b86862 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
@@ -26,11 +26,22 @@
 import android.graphics.PorterDuff
 import android.graphics.PorterDuffColorFilter
 import android.graphics.PorterDuffXfermode
+import android.graphics.Rect
+import android.graphics.Region
 import android.graphics.drawable.Drawable
 import android.hardware.graphics.common.AlphaInterpretation
 import android.hardware.graphics.common.DisplayDecorationSupport
+import android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM
+import android.view.DisplayCutout.BOUNDS_POSITION_LEFT
+import android.view.DisplayCutout.BOUNDS_POSITION_LENGTH
+import android.view.DisplayCutout.BOUNDS_POSITION_TOP
+import android.view.DisplayCutout.BOUNDS_POSITION_RIGHT
 import android.view.RoundedCorner
 import android.view.RoundedCorners
+import android.view.Surface
+import androidx.annotation.VisibleForTesting
+import kotlin.math.ceil
+import kotlin.math.floor
 
 /**
  * When the HWC of the device supports Composition.DISPLAY_DECORATON, we use this layer to draw
@@ -38,13 +49,16 @@
  */
 class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDecorationSupport)
     : DisplayCutoutBaseView(context) {
-    public val colorMode: Int
+    val colorMode: Int
     private val useInvertedAlphaColor: Boolean
     private val color: Int
     private val bgColor: Int
     private val cornerFilter: ColorFilter
     private val cornerBgFilter: ColorFilter
     private val clearPaint: Paint
+    @JvmField val transparentRect: Rect = Rect()
+    private val debugTransparentRegionPaint: Paint?
+    private val tempRect: Rect = Rect()
 
     private var roundedCornerTopSize = 0
     private var roundedCornerBottomSize = 0
@@ -61,6 +75,10 @@
             bgColor = Color.TRANSPARENT
             colorMode = ActivityInfo.COLOR_MODE_DEFAULT
             useInvertedAlphaColor = false
+            debugTransparentRegionPaint = Paint().apply {
+                color = 0x2f00ff00 // semi-transparent green
+                style = Paint.Style.FILL
+            }
         } else {
             colorMode = ActivityInfo.COLOR_MODE_A8
             useInvertedAlphaColor = displayDecorationSupport.alphaInterpretation ==
@@ -72,6 +90,7 @@
                 color = Color.BLACK
                 bgColor = Color.TRANSPARENT
             }
+            debugTransparentRegionPaint = null
         }
         cornerFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)
         cornerBgFilter = PorterDuffColorFilter(bgColor, PorterDuff.Mode.SRC_OUT)
@@ -82,6 +101,9 @@
 
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
+        if (!DEBUG_COLOR) {
+            parent.requestTransparentRegion(this)
+        }
         viewRootImpl.setDisplayDecoration(true)
 
         if (useInvertedAlphaColor) {
@@ -93,12 +115,172 @@
     }
 
     override fun onDraw(canvas: Canvas) {
+        // If updating onDraw, also update gatherTransparentRegion
         if (useInvertedAlphaColor) {
             canvas.drawColor(bgColor)
         }
         // Cutouts are drawn in DisplayCutoutBaseView.onDraw()
         super.onDraw(canvas)
         drawRoundedCorners(canvas)
+
+        debugTransparentRegionPaint?.let {
+            calculateTransparentRect()
+            canvas.drawRect(transparentRect, it)
+        }
+    }
+
+    override fun gatherTransparentRegion(region: Region?): Boolean {
+        region?.let {
+            calculateTransparentRect()
+            region.op(transparentRect, Region.Op.INTERSECT)
+        }
+        // Always return false - views underneath this should always be visible.
+        return false
+    }
+
+    /**
+     * The transparent rect is calculated by subtracting the regions of cutouts, cutout protect and
+     * rounded corners from the region with fullscreen display size.
+     */
+    @VisibleForTesting
+    fun calculateTransparentRect() {
+        transparentRect.set(0, 0, width, height)
+
+        // Remove cutout region.
+        removeCutoutFromTransparentRegion()
+
+        // Remove cutout protection region.
+        removeCutoutProtectionFromTransparentRegion()
+
+        // Remove rounded corner region.
+        removeRoundedCornersFromTransparentRegion()
+    }
+
+    private fun removeCutoutFromTransparentRegion() {
+        displayInfo.displayCutout?.let {
+                cutout ->
+            if (!cutout.boundingRectLeft.isEmpty) {
+                transparentRect.left =
+                    cutout.boundingRectLeft.right.coerceAtLeast(transparentRect.left)
+            }
+            if (!cutout.boundingRectTop.isEmpty) {
+                transparentRect.top =
+                    cutout.boundingRectTop.bottom.coerceAtLeast(transparentRect.top)
+            }
+            if (!cutout.boundingRectRight.isEmpty) {
+                transparentRect.right =
+                    cutout.boundingRectRight.left.coerceAtMost(transparentRect.right)
+            }
+            if (!cutout.boundingRectBottom.isEmpty) {
+                transparentRect.bottom =
+                    cutout.boundingRectBottom.top.coerceAtMost(transparentRect.bottom)
+            }
+        }
+    }
+
+    private fun removeCutoutProtectionFromTransparentRegion() {
+        if (protectionRect.isEmpty) {
+            return
+        }
+
+        val centerX = protectionRect.centerX()
+        val centerY = protectionRect.centerY()
+        val scaledDistanceX = (centerX - protectionRect.left) * cameraProtectionProgress
+        val scaledDistanceY = (centerY - protectionRect.top) * cameraProtectionProgress
+        tempRect.set(
+            floor(centerX - scaledDistanceX).toInt(),
+            floor(centerY - scaledDistanceY).toInt(),
+            ceil(centerX + scaledDistanceX).toInt(),
+            ceil(centerY + scaledDistanceY).toInt()
+        )
+
+        // Find out which edge the protectionRect belongs and remove that edge from the transparent
+        // region.
+        val leftDistance = tempRect.left
+        val topDistance = tempRect.top
+        val rightDistance = width - tempRect.right
+        val bottomDistance = height - tempRect.bottom
+        val minDistance = minOf(leftDistance, topDistance, rightDistance, bottomDistance)
+        when (minDistance) {
+            leftDistance -> {
+                transparentRect.left = tempRect.right.coerceAtLeast(transparentRect.left)
+            }
+            topDistance -> {
+                transparentRect.top = tempRect.bottom.coerceAtLeast(transparentRect.top)
+            }
+            rightDistance -> {
+                transparentRect.right = tempRect.left.coerceAtMost(transparentRect.right)
+            }
+            bottomDistance -> {
+                transparentRect.bottom = tempRect.top.coerceAtMost(transparentRect.bottom)
+            }
+        }
+    }
+
+    private fun removeRoundedCornersFromTransparentRegion() {
+        var hasTopOrBottomCutouts = false
+        var hasLeftOrRightCutouts = false
+        displayInfo.displayCutout?.let {
+                cutout ->
+            hasTopOrBottomCutouts = !cutout.boundingRectTop.isEmpty ||
+                    !cutout.boundingRectBottom.isEmpty
+            hasLeftOrRightCutouts = !cutout.boundingRectLeft.isEmpty ||
+                    !cutout.boundingRectRight.isEmpty
+        }
+        // The goal is to remove the rounded corner areas as small as possible so that we can have a
+        // larger transparent region. Therefore, we should always remove from the short edge sides
+        // if possible.
+        val isShortEdgeTopBottom = width < height
+        if (isShortEdgeTopBottom) {
+            // Short edges on top & bottom.
+            if (!hasTopOrBottomCutouts && hasLeftOrRightCutouts) {
+                // If there are cutouts only on left or right edges, remove left and right sides
+                // for rounded corners.
+                transparentRect.left = getRoundedCornerSizeByPosition(BOUNDS_POSITION_LEFT)
+                    .coerceAtLeast(transparentRect.left)
+                transparentRect.right =
+                    (width - getRoundedCornerSizeByPosition(BOUNDS_POSITION_RIGHT))
+                        .coerceAtMost(transparentRect.right)
+            } else {
+                // If there are cutouts on top or bottom edges or no cutout at all, remove top
+                // and bottom sides for rounded corners.
+                transparentRect.top = getRoundedCornerSizeByPosition(BOUNDS_POSITION_TOP)
+                    .coerceAtLeast(transparentRect.top)
+                transparentRect.bottom =
+                    (height - getRoundedCornerSizeByPosition(BOUNDS_POSITION_BOTTOM))
+                        .coerceAtMost(transparentRect.bottom)
+            }
+        } else {
+            // Short edges on left & right.
+            if (hasTopOrBottomCutouts && !hasLeftOrRightCutouts) {
+                // If there are cutouts only on top or bottom edges, remove top and bottom sides
+                // for rounded corners.
+                transparentRect.top = getRoundedCornerSizeByPosition(BOUNDS_POSITION_TOP)
+                    .coerceAtLeast(transparentRect.top)
+                transparentRect.bottom =
+                    (height - getRoundedCornerSizeByPosition(BOUNDS_POSITION_BOTTOM))
+                        .coerceAtMost(transparentRect.bottom)
+            } else {
+                // If there are cutouts on left or right edges or no cutout at all, remove left
+                // and right sides for rounded corners.
+                transparentRect.left = getRoundedCornerSizeByPosition(BOUNDS_POSITION_LEFT)
+                    .coerceAtLeast(transparentRect.left)
+                transparentRect.right =
+                    (width - getRoundedCornerSizeByPosition(BOUNDS_POSITION_RIGHT))
+                        .coerceAtMost(transparentRect.right)
+            }
+        }
+    }
+
+    private fun getRoundedCornerSizeByPosition(position: Int): Int {
+        val delta = displayRotation - Surface.ROTATION_0
+        return when ((position + delta) % BOUNDS_POSITION_LENGTH) {
+            BOUNDS_POSITION_LEFT -> roundedCornerTopSize.coerceAtLeast(roundedCornerBottomSize)
+            BOUNDS_POSITION_TOP -> roundedCornerTopSize
+            BOUNDS_POSITION_RIGHT -> roundedCornerTopSize.coerceAtLeast(roundedCornerBottomSize)
+            BOUNDS_POSITION_BOTTOM -> roundedCornerBottomSize
+            else -> throw IllegalArgumentException("Incorrect position: $position")
+        }
     }
 
     private fun drawRoundedCorners(canvas: Canvas) {
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 2ec5f4f..ae41cae 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -847,14 +847,20 @@
         pw.println("  mIsRoundedCornerMultipleRadius:" + mIsRoundedCornerMultipleRadius);
         pw.println("  mIsPrivacyDotEnabled:" + isPrivacyDotEnabled());
         pw.println("  mPendingRotationChange:" + mPendingRotationChange);
-        pw.println("  mHwcScreenDecorationSupport:");
-        if (mHwcScreenDecorationSupport == null) {
-            pw.println("    null");
-        } else {
-            pw.println("    format: "
+        if (mHwcScreenDecorationSupport != null) {
+            pw.println("  mHwcScreenDecorationSupport:");
+            pw.println("    format="
                     + PixelFormat.formatToString(mHwcScreenDecorationSupport.format));
-            pw.println("    alphaInterpretation: "
+            pw.println("    alphaInterpretation="
                     + alphaInterpretationToString(mHwcScreenDecorationSupport.alphaInterpretation));
+        } else {
+            pw.println("  mHwcScreenDecorationSupport: null");
+        }
+        if (mScreenDecorHwcLayer != null) {
+            pw.println("  mScreenDecorHwcLayer:");
+            pw.println("    transparentRegion=" + mScreenDecorHwcLayer.transparentRect);
+        } else {
+            pw.println("  mScreenDecorHwcLayer: null");
         }
         pw.println("  mRoundedDefault(x,y)=(" + mRoundedDefault.x + "," + mRoundedDefault.y + ")");
         pw.println("  mRoundedDefaultTop(x,y)=(" + mRoundedDefaultTop.x + "," + mRoundedDefaultTop.y
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 3ece3cc..24a655c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.content.res.Configuration;
+import android.util.Log;
 import android.util.MathUtils;
 import android.view.MotionEvent;
 
@@ -47,6 +48,7 @@
  * Class that coordinates non-HBM animations during keyguard authentication.
  */
 public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<UdfpsKeyguardView> {
+    public static final String TAG = "UdfpsKeyguardViewCtrl";
     @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
     @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @NonNull private final LockscreenShadeTransitionController mLockScreenShadeTransitionController;
@@ -246,7 +248,12 @@
         }
 
         if (mInputBouncerHiddenAmount < .5f || mIsBouncerVisible) {
-            return true;
+            if (!getStatusBarStateController().isDozing()) {
+                return true;
+            } else {
+                Log.e(TAG, "Bouncer state claims visible on doze hiddenAmount="
+                        + mInputBouncerHiddenAmount + " bouncerVisible=" + mIsBouncerVisible);
+            }
         }
 
         return false;
diff --git a/packages/SystemUI/src/com/android/systemui/chooser/ChooserActivity.java b/packages/SystemUI/src/com/android/systemui/chooser/ChooserActivity.java
deleted file mode 100644
index 28a3808..0000000
--- a/packages/SystemUI/src/com/android/systemui/chooser/ChooserActivity.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2017 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.chooser;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-/**
- * Activity for selecting which application ought to handle an ACTION_SEND intent.
- */
-public final class ChooserActivity extends Activity {
-
-    private static final String TAG = "ChooserActivity";
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        ChooserHelper.onChoose(this);
-        finish();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/chooser/ChooserHelper.java b/packages/SystemUI/src/com/android/systemui/chooser/ChooserHelper.java
deleted file mode 100644
index 6c1da47..0000000
--- a/packages/SystemUI/src/com/android/systemui/chooser/ChooserHelper.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2017 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.chooser;
-
-import android.app.Activity;
-import android.app.ActivityTaskManager;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.StrictMode;
-
-/**
- * When a target is chosen from the SystemUI Chooser activity, unpack its arguments and
- * startActivityAsCaller to handle the now-chosen intent.
- */
-public class ChooserHelper {
-
-    private static final String TAG = "ChooserHelper";
-
-    static void onChoose(Activity activity) {
-        final Intent thisIntent = activity.getIntent();
-        final Bundle thisExtras = thisIntent.getExtras();
-        final Intent chosenIntent = thisIntent.getParcelableExtra(Intent.EXTRA_INTENT);
-        final Bundle options = thisIntent.getParcelableExtra(ActivityTaskManager.EXTRA_OPTIONS);
-        final IBinder permissionToken =
-                thisExtras.getBinder(ActivityTaskManager.EXTRA_PERMISSION_TOKEN);
-        final boolean ignoreTargetSecurity =
-                thisIntent.getBooleanExtra(ActivityTaskManager.EXTRA_IGNORE_TARGET_SECURITY, false);
-        final int userId = thisIntent.getIntExtra(Intent.EXTRA_USER_ID, -1);
-
-        // We're dispatching intents that might be coming from legacy apps, so
-        // (as in com.android.internal.app.ResolverActivity) exempt ourselves from death.
-        StrictMode.disableDeathOnFileUriExposure();
-        try {
-            activity.startActivityAsCaller(
-                    chosenIntent, options, permissionToken, ignoreTargetSecurity, userId);
-        } finally {
-            StrictMode.enableDeathOnFileUriExposure();
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index a17ddc8..56e4d7e 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -166,7 +166,7 @@
         mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip));
         mDismissButton = requireNonNull(mView.findViewById(R.id.dismiss_button));
 
-        mView.setOnDismissCallback(this::hideImmediate);
+        mView.setOnDismissEndCallback(this::hideImmediate);
         mView.setOnInteractionCallback(mTimeoutHandler::resetTimeout);
 
         mDismissButton.setOnClickListener(view -> animateOut());
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java
index 8843462..a327809 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.clipboardoverlay;
 
+import android.animation.Animator;
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
@@ -28,13 +29,16 @@
 import com.android.systemui.R;
 import com.android.systemui.screenshot.SwipeDismissHandler;
 
+import java.util.function.Consumer;
+
 /**
  * ConstraintLayout that is draggable when touched in a specific region
  */
 public class DraggableConstraintLayout extends ConstraintLayout {
     private final SwipeDismissHandler mSwipeDismissHandler;
     private final GestureDetector mSwipeDetector;
-    private Runnable mOnDismiss;
+    private Consumer<Animator> mOnDismissInitiated;
+    private Runnable mOnDismissComplete;
     private Runnable mOnInteraction;
 
     public DraggableConstraintLayout(Context context) {
@@ -58,9 +62,16 @@
                     }
 
                     @Override
-                    public void onDismiss() {
-                        if (mOnDismiss != null) {
-                            mOnDismiss.run();
+                    public void onSwipeDismissInitiated(Animator animator) {
+                        if (mOnDismissInitiated != null) {
+                            mOnDismissInitiated.accept(animator);
+                        }
+                    }
+
+                    @Override
+                    public void onDismissComplete() {
+                        if (mOnDismissComplete != null) {
+                            mOnDismissComplete.run();
                         }
                     }
                 });
@@ -106,10 +117,18 @@
     }
 
     /**
+     * Set the callback to be run after view is dismissed (before animation; receives animator as
+     * input)
+     */
+    public void setOnDismissStartCallback(Consumer<Animator> callback) {
+        mOnDismissInitiated = callback;
+    }
+
+    /**
      * Set the callback to be run after view is dismissed
      */
-    public void setOnDismissCallback(Runnable callback) {
-        mOnDismiss = callback;
+    public void setOnDismissEndCallback(Runnable callback) {
+        mOnDismissComplete = callback;
     }
 
     /**
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 f87fa96..5c1d8c3 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -23,7 +23,11 @@
 import android.content.Intent
 import android.content.pm.PackageManager
 import android.content.pm.ResolveInfo
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
 import android.os.VibrationEffect
+import android.provider.Settings
 import android.service.controls.Control
 import android.service.controls.actions.BooleanAction
 import android.service.controls.actions.CommandAction
@@ -38,6 +42,7 @@
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.settings.SecureSettings
 import com.android.wm.shell.TaskViewFactory
 import java.util.Optional
 import javax.inject.Inject
@@ -51,19 +56,41 @@
     private val keyguardStateController: KeyguardStateController,
     private val taskViewFactory: Optional<TaskViewFactory>,
     private val controlsMetricsLogger: ControlsMetricsLogger,
-    private val vibrator: VibratorHelper
+    private val vibrator: VibratorHelper,
+    private val secureSettings: SecureSettings,
+    @Main mainHandler: Handler
 ) : ControlActionCoordinator {
     private var dialog: Dialog? = null
     private var pendingAction: Action? = null
     private var actionsInProgress = mutableSetOf<String>()
     private val isLocked: Boolean
         get() = !keyguardStateController.isUnlocked()
+    private var mAllowTrivialControls: Boolean = secureSettings.getInt(
+            Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, 0) != 0
     override lateinit var activityContext: Context
 
     companion object {
         private const val RESPONSE_TIMEOUT_IN_MILLIS = 3000L
     }
 
+    init {
+        val lockScreenShowControlsUri =
+            secureSettings.getUriFor(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS)
+        val controlsContentObserver = object : ContentObserver(mainHandler) {
+            override fun onChange(selfChange: Boolean, uri: Uri?) {
+                super.onChange(selfChange, uri)
+                if (uri == lockScreenShowControlsUri) {
+                    mAllowTrivialControls = secureSettings.getInt(
+                            Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, 0) != 0
+                }
+            }
+        }
+        secureSettings.registerContentObserver(
+            lockScreenShowControlsUri,
+            false /* notifyForDescendants */, controlsContentObserver
+        )
+    }
+
     override fun closeDialogs() {
         dialog?.dismiss()
         dialog = null
@@ -80,7 +107,7 @@
                 },
                 true /* blockable */
             ),
-            isAuthRequired(cvh)
+            isAuthRequired(cvh, mAllowTrivialControls)
         )
     }
 
@@ -100,7 +127,7 @@
                 },
                 blockable
             ),
-            isAuthRequired(cvh)
+            isAuthRequired(cvh, mAllowTrivialControls)
         )
     }
 
@@ -120,7 +147,7 @@
                 { cvh.action(FloatAction(templateId, newValue)) },
                 false /* blockable */
             ),
-            isAuthRequired(cvh)
+            isAuthRequired(cvh, mAllowTrivialControls)
         )
     }
 
@@ -139,7 +166,7 @@
                 },
                 false /* blockable */
             ),
-            isAuthRequired(cvh)
+            isAuthRequired(cvh, mAllowTrivialControls)
         )
     }
 
@@ -156,7 +183,11 @@
         actionsInProgress.remove(controlId)
     }
 
-    private fun isAuthRequired(cvh: ControlViewHolder) = cvh.cws.control?.isAuthRequired() ?: true
+    @VisibleForTesting()
+    fun isAuthRequired(cvh: ControlViewHolder, allowTrivialControls: Boolean): Boolean {
+        val isAuthRequired = cvh.cws.control?.isAuthRequired ?: true
+        return isAuthRequired || !allowTrivialControls
+    }
 
     private fun shouldRunAction(controlId: String) =
         if (actionsInProgress.add(controlId)) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index bd472a4..71c538d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -71,6 +71,7 @@
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.CaptioningManager;
 import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.app.IBatteryStats;
@@ -120,6 +121,12 @@
 
     @Provides
     @Singleton
+    static CaptioningManager provideCaptioningManager(Context context) {
+        return context.getSystemService(CaptioningManager.class);
+    }
+
+    @Provides
+    @Singleton
     static ColorDisplayManager provideColorDisplayManager(Context context) {
         return context.getSystemService(ColorDisplayManager.class);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index b8b4092..dfb27ef 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -89,6 +89,7 @@
     private boolean mPaused = false;
     private boolean mScreenOff = false;
     private int mLastSensorValue = -1;
+    private DozeMachine.State mState = DozeMachine.State.UNINITIALIZED;
 
     /**
      * Debug value used for emulating various display brightness buckets:
@@ -135,6 +136,7 @@
 
     @Override
     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
+        mState = newState;
         switch (newState) {
             case INITIALIZED:
                 resetBrightnessToDefault();
@@ -262,8 +264,9 @@
      */
     private int clampToDimBrightnessForScreenOff(int brightness) {
         final boolean screenTurningOff =
-                mDozeParameters.shouldClampToDimBrightness()
-                        || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_GOING_TO_SLEEP;
+                (mDozeParameters.shouldClampToDimBrightness()
+                        || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_GOING_TO_SLEEP)
+                && mState == DozeMachine.State.INITIALIZED;
         if (screenTurningOff
                 && mWakefulnessLifecycle.getLastSleepReason() == GO_TO_SLEEP_REASON_TIMEOUT) {
             return Math.max(
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index be76e8f..7450103 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -18,12 +18,9 @@
 
 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
 
-import android.graphics.Rect;
-import android.graphics.Region;
 import android.os.Handler;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
@@ -62,43 +59,6 @@
     // Main thread handler used to schedule periodic tasks (e.g. burn-in protection updates).
     private final Handler mHandler;
 
-    // A hook into the internal inset calculation where we declare the overlays as the only
-    // touchable regions.
-    private final ViewTreeObserver.OnComputeInternalInsetsListener
-            mOnComputeInternalInsetsListener =
-            new ViewTreeObserver.OnComputeInternalInsetsListener() {
-                @Override
-                public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
-                    inoutInfo.setTouchableInsets(
-                            ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
-                    final Region region = new Region();
-                    final Rect rect = new Rect();
-                    final int childCount = mDreamOverlayContentView.getChildCount();
-                    for (int i = 0; i < childCount; i++) {
-                        View child = mDreamOverlayContentView.getChildAt(i);
-
-                        if (mComplicationHostViewController.getView() == child) {
-                            region.op(mComplicationHostViewController.getTouchRegions(),
-                                    Region.Op.UNION);
-                            continue;
-                        }
-
-                        if (child.getGlobalVisibleRect(rect)) {
-                            region.op(rect, Region.Op.UNION);
-                        }
-                    }
-
-                    // Add the notifications drag area to the tap region (otherwise the
-                    // notifications shade can't be dragged down).
-                    if (mDreamOverlayContentView.getGlobalVisibleRect(rect)) {
-                        rect.bottom = rect.top + mDreamOverlayNotificationsDragAreaHeight;
-                        region.op(rect, Region.Op.UNION);
-                    }
-
-                    inoutInfo.touchableRegion.set(region);
-                }
-            };
-
     @Inject
     public DreamOverlayContainerViewController(
             DreamOverlayContainerView containerView,
@@ -136,16 +96,12 @@
 
     @Override
     protected void onViewAttached() {
-        mView.getViewTreeObserver()
-                .addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
         mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval);
     }
 
     @Override
     protected void onViewDetached() {
         mHandler.removeCallbacks(this::updateBurnInOffsets);
-        mView.getViewTreeObserver()
-                .removeOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
     }
 
     View getContainerView() {
@@ -162,6 +118,7 @@
         // so no translation occurs when the values don't change.
         mView.setTranslationX(getBurnInOffset(mMaxBurnInOffset * 2, true)
                 - mMaxBurnInOffset);
+
         mView.setTranslationY(getBurnInOffset(mMaxBurnInOffset * 2, false)
                 - mMaxBurnInOffset);
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayDotImageView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayDotImageView.java
new file mode 100644
index 0000000..02a8b39a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayDotImageView.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams;
+
+import android.annotation.ColorInt;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.AlphaOptimizedImageView;
+
+/**
+ * An {@link AlphaOptimizedImageView} that is responsible for rendering a dot. Used by
+ * {@link DreamOverlayStatusBarView}.
+ */
+public class DreamOverlayDotImageView extends AlphaOptimizedImageView {
+    private final @ColorInt int mDotColor;
+
+    public DreamOverlayDotImageView(Context context) {
+        this(context, null);
+    }
+
+    public DreamOverlayDotImageView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public DreamOverlayDotImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public DreamOverlayDotImageView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
+                R.styleable.DreamOverlayDotImageView, 0, 0);
+
+        try {
+            mDotColor = a.getColor(R.styleable.DreamOverlayDotImageView_dotColor, Color.WHITE);
+        } finally {
+            a.recycle();
+        }
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        setImageDrawable(new DotDrawable(mDotColor));
+    }
+
+    private static class DotDrawable extends Drawable {
+        private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        private Bitmap mDotBitmap;
+        private final Rect mBounds = new Rect();
+        private final @ColorInt int mDotColor;
+
+        DotDrawable(@ColorInt int color) {
+            mDotColor = color;
+        }
+
+        @Override
+        public void draw(@NonNull Canvas canvas) {
+            if (mBounds.isEmpty()) {
+                return;
+            }
+
+            if (mDotBitmap == null) {
+                mDotBitmap = createBitmap(mBounds.width(), mBounds.height());
+            }
+
+            canvas.drawBitmap(mDotBitmap, null, mBounds, mPaint);
+        }
+
+        @Override
+        protected void onBoundsChange(Rect bounds) {
+            super.onBoundsChange(bounds);
+            mBounds.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
+            // Make sure to regenerate the dot bitmap when the bounds change.
+            mDotBitmap = null;
+        }
+
+        @Override
+        public void setAlpha(int alpha) {
+        }
+
+        @Override
+        public void setColorFilter(@Nullable ColorFilter colorFilter) {
+        }
+
+        @Override
+        public int getOpacity() {
+            return 0;
+        }
+
+        private Bitmap createBitmap(int width, int height) {
+            Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            Canvas canvas = new Canvas(bitmap);
+            Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+            paint.setColor(mDotColor);
+            canvas.drawCircle(width / 2.f, height / 2.f, Math.min(width, height) / 2.f, paint);
+            return bitmap;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 338a8b2..7e1fce2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -33,6 +33,7 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.dreams.complication.DreamPreviewComplication;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
 
@@ -57,6 +58,7 @@
     // content area).
     private final DreamOverlayContainerViewController mDreamOverlayContainerViewController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final DreamPreviewComplication mPreviewComplication;
 
     // A reference to the {@link Window} used to hold the dream overlay.
     private Window mWindow;
@@ -96,12 +98,14 @@
             @Main Executor executor,
             DreamOverlayComponent.Factory dreamOverlayComponentFactory,
             DreamOverlayStateController stateController,
-            KeyguardUpdateMonitor keyguardUpdateMonitor) {
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            DreamPreviewComplication previewComplication) {
         mContext = context;
         mExecutor = executor;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
         mStateController = stateController;
+        mPreviewComplication = previewComplication;
 
         final DreamOverlayComponent component =
                 dreamOverlayComponentFactory.create(mViewModelStore, mHost);
@@ -125,6 +129,9 @@
             windowManager.removeView(mWindow.getDecorView());
         }
         mStateController.setOverlayActive(false);
+        mPreviewComplication.setDreamLabel(null);
+        mStateController.removeComplication(mPreviewComplication);
+        mStateController.setPreviewMode(false);
         super.onDestroy();
     }
 
@@ -133,6 +140,11 @@
         setCurrentState(Lifecycle.State.STARTED);
         mExecutor.execute(() -> {
             mStateController.setShouldShowComplications(shouldShowComplications());
+            mStateController.setPreviewMode(isPreviewMode());
+            if (isPreviewMode()) {
+                mPreviewComplication.setDreamLabel(getDreamLabel());
+                mStateController.addComplication(mPreviewComplication);
+            }
             addOverlayWindowLocked(layoutParams);
             setCurrentState(Lifecycle.State.RESUMED);
             mStateController.setOverlayActive(true);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index fc71e2f..6860998 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -50,6 +50,7 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
+    public static final int STATE_PREVIEW_MODE = 1 << 1;
 
     private static final int OP_CLEAR_STATE = 1;
     private static final int OP_SET_STATE = 2;
@@ -249,4 +250,18 @@
             mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
         });
     }
+
+    /**
+     * Sets whether the dream is running in preview mode.
+     */
+    public void setPreviewMode(boolean isPreviewMode) {
+        modifyState(isPreviewMode ? OP_SET_STATE : OP_CLEAR_STATE, STATE_PREVIEW_MODE);
+    }
+
+    /**
+     * Returns whether the dream is running in preview mode.
+     */
+    public boolean isPreviewMode() {
+        return containsState(STATE_PREVIEW_MODE);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
index 9847ef6..2d96920 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
@@ -25,17 +25,13 @@
 
 import com.android.internal.util.Preconditions;
 import com.android.systemui.R;
-import com.android.systemui.battery.BatteryMeterView;
-import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 
 /**
  * {@link DreamOverlayStatusBarView} is the view responsible for displaying the status bar in a
- * dream. The status bar includes status icons such as battery and wifi.
+ * dream. The status bar displays conditional status icons such as "priority mode" and "no wifi".
  */
-public class DreamOverlayStatusBarView extends ConstraintLayout implements
-        BatteryStateChangeCallback {
+public class DreamOverlayStatusBarView extends ConstraintLayout {
 
-    private BatteryMeterView mBatteryView;
     private ImageView mWifiStatusView;
 
     public DreamOverlayStatusBarView(Context context) {
@@ -59,20 +55,8 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        mBatteryView = Preconditions.checkNotNull(findViewById(R.id.dream_overlay_battery),
-                "R.id.dream_overlay_battery must not be null");
         mWifiStatusView = Preconditions.checkNotNull(findViewById(R.id.dream_overlay_wifi_status),
                 "R.id.dream_overlay_wifi_status must not be null");
-
-        mWifiStatusView.setImageDrawable(getContext().getDrawable(R.drawable.ic_signal_wifi_off));
-    }
-
-    /**
-     * Whether to show the battery percent text next to the battery status icons.
-     * @param show True if the battery percent text should be shown.
-     */
-    void showBatteryPercentText(boolean show) {
-        mBatteryView.setForceShowPercent(show);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index 5674b9f..32b2309 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -17,24 +17,20 @@
 package com.android.systemui.dreams;
 
 import android.annotation.IntDef;
-import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 
-import com.android.systemui.battery.BatteryMeterViewController;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
-import com.android.systemui.dreams.dagger.DreamOverlayModule;
-import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.touch.TouchInsetManager;
 import com.android.systemui.util.ViewController;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 
 /**
  * View controller for {@link DreamOverlayStatusBarView}.
@@ -52,21 +48,8 @@
     private static final int WIFI_STATUS_UNAVAILABLE = 1;
     private static final int WIFI_STATUS_AVAILABLE = 2;
 
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = { "BATTERY_STATUS_" }, value = {
-            BATTERY_STATUS_UNKNOWN,
-            BATTERY_STATUS_NOT_CHARGING,
-            BATTERY_STATUS_CHARGING
-    })
-    private @interface BatteryStatus {}
-    private static final int BATTERY_STATUS_UNKNOWN = 0;
-    private static final int BATTERY_STATUS_NOT_CHARGING = 1;
-    private static final int BATTERY_STATUS_CHARGING = 2;
-
-    private final BatteryController mBatteryController;
-    private final BatteryMeterViewController mBatteryMeterViewController;
     private final ConnectivityManager mConnectivityManager;
-    private final boolean mShowPercentAvailable;
+    private final TouchInsetManager.TouchInsetSession mTouchInsetSession;
 
     private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
             .clearCapabilities()
@@ -91,43 +74,20 @@
         }
     };
 
-    private final BatteryController.BatteryStateChangeCallback mBatteryStateChangeCallback =
-            new BatteryController.BatteryStateChangeCallback() {
-                @Override
-                public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
-                    DreamOverlayStatusBarViewController.this.onBatteryLevelChanged(charging);
-                }
-            };
-
     private @WifiStatus int mWifiStatus = WIFI_STATUS_UNKNOWN;
-    private @BatteryStatus int mBatteryStatus = BATTERY_STATUS_UNKNOWN;
 
     @Inject
     public DreamOverlayStatusBarViewController(
-            Context context,
             DreamOverlayStatusBarView view,
-            BatteryController batteryController,
-            @Named(DreamOverlayModule.DREAM_OVERLAY_BATTERY_CONTROLLER)
-                    BatteryMeterViewController batteryMeterViewController,
-            ConnectivityManager connectivityManager) {
+            ConnectivityManager connectivityManager,
+            TouchInsetManager.TouchInsetSession touchInsetSession) {
         super(view);
-        mBatteryController = batteryController;
-        mBatteryMeterViewController = batteryMeterViewController;
         mConnectivityManager = connectivityManager;
-
-        mShowPercentAvailable = context.getResources().getBoolean(
-                com.android.internal.R.bool.config_battery_percentage_setting_available);
-    }
-
-    @Override
-    protected void onInit() {
-        super.onInit();
-        mBatteryMeterViewController.init();
+        mTouchInsetSession = touchInsetSession;
     }
 
     @Override
     protected void onViewAttached() {
-        mBatteryController.addCallback(mBatteryStateChangeCallback);
         mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
 
         NetworkCapabilities capabilities =
@@ -136,12 +96,13 @@
         onWifiAvailabilityChanged(
                 capabilities != null
                         && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI));
+        mTouchInsetSession.addViewToTracking(mView);
     }
 
     @Override
     protected void onViewDetached() {
-        mBatteryController.removeCallback(mBatteryStateChangeCallback);
         mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+        mTouchInsetSession.clear();
     }
 
     /**
@@ -155,18 +116,4 @@
             mView.showWifiStatus(mWifiStatus == WIFI_STATUS_UNAVAILABLE);
         }
     }
-
-    /**
-     * The battery level has changed. Update the battery status icon as appropriate.
-     * @param charging Whether the battery is currently charging.
-     */
-    private void onBatteryLevelChanged(boolean charging) {
-        final int newBatteryStatus =
-                charging ? BATTERY_STATUS_CHARGING : BATTERY_STATUS_NOT_CHARGING;
-        if (mBatteryStatus != newBatteryStatus) {
-            mBatteryStatus = newBatteryStatus;
-            mView.showBatteryPercentText(
-                    mBatteryStatus == BATTERY_STATUS_CHARGING && mShowPercentAvailable);
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
index 240389a..a5dcd39 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
@@ -59,7 +59,19 @@
 
         @Override
         public void start() {
-            if (mSmartSpaceController.isEnabled()) {
+            addOrRemoveOverlay();
+            mDreamOverlayStateController.addCallback(new DreamOverlayStateController.Callback() {
+                @Override
+                public void onStateChanged() {
+                    addOrRemoveOverlay();
+                }
+            });
+        }
+
+        private void addOrRemoveOverlay() {
+            if (mDreamOverlayStateController.isPreviewMode()) {
+                mDreamOverlayStateController.removeComplication(mComplication);
+            } else if (mSmartSpaceController.isEnabled()) {
                 mDreamOverlayStateController.addComplication(mComplication);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
index 0b80d8a..437e799 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -27,6 +27,7 @@
 import androidx.constraintlayout.widget.Constraints;
 
 import com.android.systemui.R;
+import com.android.systemui.touch.TouchInsetManager;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -52,6 +53,7 @@
     private static class ViewEntry implements Comparable<ViewEntry> {
         private final View mView;
         private final ComplicationLayoutParams mLayoutParams;
+        private final TouchInsetManager.TouchInsetSession mTouchInsetSession;
         private final Parent mParent;
         @Complication.Category
         private final int mCategory;
@@ -61,7 +63,8 @@
          * Default constructor. {@link Parent} allows for the {@link ViewEntry}'s surrounding
          * view hierarchy to be accessed without traversing the entire view tree.
          */
-        ViewEntry(View view, ComplicationLayoutParams layoutParams, int category, Parent parent,
+        ViewEntry(View view, ComplicationLayoutParams layoutParams,
+                TouchInsetManager.TouchInsetSession touchSession, int category, Parent parent,
                 int margin) {
             mView = view;
             // Views that are generated programmatically do not have a unique id assigned to them
@@ -70,9 +73,12 @@
             // {@link Complication.ViewHolder} should not reference the root container by id.
             mView.setId(View.generateViewId());
             mLayoutParams = layoutParams;
+            mTouchInsetSession = touchSession;
             mCategory = category;
             mParent = parent;
             mMargin = margin;
+
+            touchSession.addViewToTracking(mView);
         }
 
         /**
@@ -217,6 +223,7 @@
             mParent.removeEntry(this);
 
             ((ViewGroup) mView.getParent()).removeView(mView);
+            mTouchInsetSession.removeViewFromTracking(mView);
         }
 
         @Override
@@ -242,15 +249,18 @@
          */
         private static class Builder {
             private final View mView;
+            private final TouchInsetManager.TouchInsetSession mTouchSession;
             private final ComplicationLayoutParams mLayoutParams;
             private final int mCategory;
             private Parent mParent;
             private int mMargin;
 
-            Builder(View view, ComplicationLayoutParams lp, @Complication.Category int category) {
+            Builder(View view, TouchInsetManager.TouchInsetSession touchSession,
+                    ComplicationLayoutParams lp, @Complication.Category int category) {
                 mView = view;
                 mLayoutParams = lp;
                 mCategory = category;
+                mTouchSession = touchSession;
             }
 
             /**
@@ -291,7 +301,8 @@
              * Builds and returns the resulting {@link ViewEntry}.
              */
             ViewEntry build() {
-                return new ViewEntry(mView, mLayoutParams, mCategory, mParent, mMargin);
+                return new ViewEntry(mView, mLayoutParams, mTouchSession, mCategory, mParent,
+                        mMargin);
             }
         }
 
@@ -442,13 +453,16 @@
     private final int mMargin;
     private final HashMap<ComplicationId, ViewEntry> mEntries = new HashMap<>();
     private final HashMap<Integer, PositionGroup> mPositions = new HashMap<>();
+    private final TouchInsetManager.TouchInsetSession mSession;
 
     /** */
     @Inject
     public ComplicationLayoutEngine(@Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout layout,
-            @Named(COMPLICATION_MARGIN) int margin) {
+            @Named(COMPLICATION_MARGIN) int margin,
+            TouchInsetManager.TouchInsetSession session) {
         mLayout = layout;
         mMargin = margin;
+        mSession = session;
     }
 
     /**
@@ -468,7 +482,7 @@
             removeComplication(id);
         }
 
-        final ViewEntry.Builder entryBuilder = new ViewEntry.Builder(view, lp, category)
+        final ViewEntry.Builder entryBuilder = new ViewEntry.Builder(view, mSession, lp, category)
                 .setMargin(mMargin);
 
         // Add position group if doesn't already exist
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamPreviewComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamPreviewComplication.java
new file mode 100644
index 0000000..23343b1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamPreviewComplication.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication;
+
+import static com.android.systemui.dreams.complication.dagger.DreamPreviewComplicationComponent.DREAM_LABEL;
+import static com.android.systemui.dreams.complication.dagger.DreamPreviewComplicationComponent.DreamPreviewComplicationModule.DREAM_PREVIEW_COMPLICATION_LAYOUT_PARAMS;
+import static com.android.systemui.dreams.complication.dagger.DreamPreviewComplicationComponent.DreamPreviewComplicationModule.DREAM_PREVIEW_COMPLICATION_VIEW;
+
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.systemui.dreams.complication.dagger.DreamPreviewComplicationComponent;
+import com.android.systemui.util.ViewController;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Preview complication shown when user is previewing a dream.
+ */
+public class DreamPreviewComplication implements Complication {
+    DreamPreviewComplicationComponent.Factory mComponentFactory;
+    @Nullable
+    private CharSequence mDreamLabel;
+
+    /**
+     * Default constructor for {@link DreamPreviewComplication}.
+     */
+    @Inject
+    public DreamPreviewComplication(
+            DreamPreviewComplicationComponent.Factory componentFactory) {
+        mComponentFactory = componentFactory;
+    }
+
+    /**
+     * Create {@link DreamPreviewViewHolder}.
+     */
+    @Override
+    public ViewHolder createView(ComplicationViewModel model) {
+        return mComponentFactory.create(model, mDreamLabel).getViewHolder();
+    }
+
+    /**
+     * Sets the user-facing label for the current dream.
+     */
+    public void setDreamLabel(@Nullable CharSequence dreamLabel) {
+        mDreamLabel = dreamLabel;
+    }
+
+    /**
+     * ViewHolder to contain value/logic associated with a Preview Complication View.
+     */
+    public static class DreamPreviewViewHolder implements ViewHolder {
+        private final TextView mView;
+        private final ComplicationLayoutParams mLayoutParams;
+        private final DreamPreviewViewController mViewController;
+
+        @Inject
+        DreamPreviewViewHolder(@Named(DREAM_PREVIEW_COMPLICATION_VIEW) TextView view,
+                DreamPreviewViewController controller,
+                @Named(DREAM_PREVIEW_COMPLICATION_LAYOUT_PARAMS)
+                        ComplicationLayoutParams layoutParams,
+                @Named(DREAM_LABEL) @Nullable CharSequence dreamLabel) {
+            mView = view;
+            mLayoutParams = layoutParams;
+            mViewController = controller;
+            mViewController.init();
+
+            if (!TextUtils.isEmpty(dreamLabel)) {
+                mView.setText(dreamLabel);
+            }
+        }
+
+        @Override
+        public View getView() {
+            return mView;
+        }
+
+        @Override
+        public ComplicationLayoutParams getLayoutParams() {
+            return mLayoutParams;
+        }
+
+        @Override
+        public int getCategory() {
+            return CATEGORY_SYSTEM;
+        }
+    }
+
+    /**
+     * ViewController to contain value/logic associated with a Preview Complication View.
+     */
+    static class DreamPreviewViewController extends ViewController<TextView> {
+        private final ComplicationViewModel mViewModel;
+
+        @Inject
+        DreamPreviewViewController(@Named(DREAM_PREVIEW_COMPLICATION_VIEW) TextView view,
+                ComplicationViewModel viewModel) {
+            super(view);
+            mViewModel = viewModel;
+        }
+
+        @Override
+        protected void onViewAttached() {
+            mView.setOnClickListener(v -> mViewModel.exitDream());
+        }
+
+        @Override
+        protected void onViewDetached() {
+            mView.setOnClickListener(null);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamPreviewComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamPreviewComplicationComponent.java
new file mode 100644
index 0000000..502e31e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamPreviewComplicationComponent.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication.dagger;
+
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+import com.android.systemui.R;
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+import com.android.systemui.dreams.complication.ComplicationViewModel;
+import com.android.systemui.dreams.complication.DreamPreviewComplication.DreamPreviewViewHolder;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Named;
+import javax.inject.Scope;
+
+import dagger.BindsInstance;
+import dagger.Module;
+import dagger.Provides;
+import dagger.Subcomponent;
+
+/**
+ * {@link DreamPreviewComplicationComponent} is responsible for generating dependencies
+ * surrounding the
+ * Preview {@link com.android.systemui.dreams.complication.Complication}, such as the layout
+ * details.
+ */
+@Subcomponent(modules = {
+        DreamPreviewComplicationComponent.DreamPreviewComplicationModule.class,
+})
+@DreamPreviewComplicationComponent.DreamPreviewComplicationScope
+public interface DreamPreviewComplicationComponent {
+    String DREAM_LABEL = "dream_label";
+
+    /**
+     * Creates {@link DreamPreviewViewHolder}.
+     */
+    DreamPreviewViewHolder getViewHolder();
+
+    @Documented
+    @Retention(RUNTIME)
+    @Scope
+    @interface DreamPreviewComplicationScope {
+    }
+
+    /**
+     * Generates {@link DreamPreviewComplicationComponent}.
+     */
+    @Subcomponent.Factory
+    interface Factory {
+        DreamPreviewComplicationComponent create(
+                @BindsInstance ComplicationViewModel viewModel,
+                @Named(DREAM_LABEL) @BindsInstance @Nullable CharSequence dreamLabel);
+    }
+
+    /**
+     * Scoped values for {@link DreamPreviewComplicationComponent}.
+     */
+    @Module
+    interface DreamPreviewComplicationModule {
+        String DREAM_PREVIEW_COMPLICATION_VIEW = "preview_complication_view";
+        String DREAM_PREVIEW_COMPLICATION_LAYOUT_PARAMS = "preview_complication_layout_params";
+        // Order weight of insert into parent container
+        int INSERT_ORDER_WEIGHT = 1000;
+
+        /**
+         * Provides the complication view.
+         */
+        @Provides
+        @DreamPreviewComplicationScope
+        @Named(DREAM_PREVIEW_COMPLICATION_VIEW)
+        static TextView provideComplicationView(LayoutInflater layoutInflater) {
+            return Preconditions.checkNotNull((TextView)
+                            layoutInflater.inflate(R.layout.dream_overlay_complication_preview,
+                                    null, false),
+                    "R.layout.dream_overlay_complication_preview did not properly inflated");
+        }
+
+        /**
+         * Provides the layout parameters for the complication view.
+         */
+        @Provides
+        @DreamPreviewComplicationScope
+        @Named(DREAM_PREVIEW_COMPLICATION_LAYOUT_PARAMS)
+        static ComplicationLayoutParams provideLayoutParams() {
+            return new ComplicationLayoutParams(0,
+                    ViewGroup.LayoutParams.WRAP_CONTENT,
+                    ComplicationLayoutParams.POSITION_TOP
+                            | ComplicationLayoutParams.POSITION_START,
+                    ComplicationLayoutParams.DIRECTION_DOWN,
+                    INSERT_ORDER_WEIGHT, /* snapToGuide= */ true);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index c61f796..65f060b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 
 import com.android.settingslib.dream.DreamBackend;
+import com.android.systemui.dreams.complication.dagger.DreamPreviewComplicationComponent;
 import com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule;
 import com.android.systemui.dreams.touch.dagger.DreamTouchModule;
 
@@ -34,6 +35,7 @@
         },
         subcomponents = {
             DreamOverlayComponent.class,
+            DreamPreviewComplicationComponent.class,
         })
 public interface DreamModule {
     /**
@@ -43,4 +45,4 @@
     static DreamBackend providesDreamBackend(Context context) {
         return DreamBackend.getInstance(context);
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 4eb5cb9..63676d6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -16,9 +16,7 @@
 
 package com.android.systemui.dreams.dagger;
 
-import android.content.ContentResolver;
 import android.content.res.Resources;
-import android.os.Handler;
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
 
@@ -28,15 +26,12 @@
 
 import com.android.internal.util.Preconditions;
 import com.android.systemui.R;
-import com.android.systemui.battery.BatteryMeterView;
-import com.android.systemui.battery.BatteryMeterViewController;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.DreamOverlayContainerView;
 import com.android.systemui.dreams.DreamOverlayStatusBarView;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.tuner.TunerService;
+import com.android.systemui.touch.TouchInsetManager;
+
+import java.util.concurrent.Executor;
 
 import javax.inject.Named;
 
@@ -47,9 +42,6 @@
 /** Dagger module for {@link DreamOverlayComponent}. */
 @Module
 public abstract class DreamOverlayModule {
-    private static final String DREAM_OVERLAY_BATTERY_VIEW = "dream_overlay_battery_view";
-    public static final String DREAM_OVERLAY_BATTERY_CONTROLLER =
-            "dream_overlay_battery_controller";
     public static final String DREAM_OVERLAY_CONTENT_VIEW = "dream_overlay_content_view";
     public static final String MAX_BURN_IN_OFFSET = "max_burn_in_offset";
     public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL =
@@ -76,6 +68,21 @@
 
     /** */
     @Provides
+    public static TouchInsetManager.TouchInsetSession providesTouchInsetSession(
+            TouchInsetManager manager) {
+        return manager.createSession();
+    }
+
+    /** */
+    @Provides
+    @DreamOverlayComponent.DreamOverlayScope
+    public static TouchInsetManager providesTouchInsetManager(@Main Executor executor,
+            DreamOverlayContainerView view) {
+        return new TouchInsetManager(executor, view);
+    }
+
+    /** */
+    @Provides
     @DreamOverlayComponent.DreamOverlayScope
     public static DreamOverlayStatusBarView providesDreamOverlayStatusBarView(
             DreamOverlayContainerView view) {
@@ -86,37 +93,6 @@
     /** */
     @Provides
     @DreamOverlayComponent.DreamOverlayScope
-    @Named(DREAM_OVERLAY_BATTERY_VIEW)
-    static BatteryMeterView providesBatteryMeterView(DreamOverlayContainerView view) {
-        return Preconditions.checkNotNull(view.findViewById(R.id.dream_overlay_battery),
-                "R.id.battery must not be null");
-    }
-
-    /** */
-    @Provides
-    @DreamOverlayComponent.DreamOverlayScope
-    @Named(DREAM_OVERLAY_BATTERY_CONTROLLER)
-    static BatteryMeterViewController providesBatteryMeterViewController(
-            @Named(DREAM_OVERLAY_BATTERY_VIEW) BatteryMeterView batteryMeterView,
-            ConfigurationController configurationController,
-            TunerService tunerService,
-            BroadcastDispatcher broadcastDispatcher,
-            @Main Handler mainHandler,
-            ContentResolver contentResolver,
-            BatteryController batteryController) {
-        return new BatteryMeterViewController(
-                batteryMeterView,
-                configurationController,
-                tunerService,
-                broadcastDispatcher,
-                mainHandler,
-                contentResolver,
-                batteryController);
-    }
-
-    /** */
-    @Provides
-    @DreamOverlayComponent.DreamOverlayScope
     @Named(MAX_BURN_IN_OFFSET)
     static int providesMaxBurnInOffset(@Main Resources resources) {
         return resources.getDimensionPixelSize(R.dimen.default_burn_in_prevention_offset);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
index 96a90df..9d6e3c2 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
@@ -28,6 +28,9 @@
     /** Returns a boolean value for the given flag.  */
     fun isEnabled(flag: ResourceBooleanFlag): Boolean
 
+    /** Returns a boolean value for the given flag.  */
+    fun isEnabled(flag: SysPropBooleanFlag): Boolean
+
     /** Returns a string value for the given flag.  */
     fun getString(flag: StringFlag): String
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index df60599..6779904 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -49,6 +49,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.TreeMap;
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 import javax.inject.Inject;
@@ -69,6 +70,7 @@
     private final FlagManager mFlagManager;
     private final SecureSettings mSecureSettings;
     private final Resources mResources;
+    private final SystemPropertiesHelper mSystemProperties;
     private final Supplier<Map<Integer, Flag<?>>> mFlagsCollector;
     private final Map<Integer, Boolean> mBooleanFlagCache = new TreeMap<>();
     private final Map<Integer, String> mStringFlagCache = new TreeMap<>();
@@ -79,6 +81,7 @@
             FlagManager flagManager,
             Context context,
             SecureSettings secureSettings,
+            SystemPropertiesHelper systemProperties,
             @Main Resources resources,
             DumpManager dumpManager,
             @Nullable Supplier<Map<Integer, Flag<?>>> flagsCollector,
@@ -86,11 +89,12 @@
         mFlagManager = flagManager;
         mSecureSettings = secureSettings;
         mResources = resources;
+        mSystemProperties = systemProperties;
         mFlagsCollector = flagsCollector != null ? flagsCollector : Flags::collectFlags;
         IntentFilter filter = new IntentFilter();
         filter.addAction(ACTION_SET_FLAG);
         filter.addAction(ACTION_GET_FLAGS);
-        flagManager.setRestartAction(this::restartSystemUI);
+        flagManager.setOnSettingsChangedAction(this::restartSystemUI);
         flagManager.setClearCacheAction(this::removeFromCache);
         context.registerReceiver(mReceiver, filter, null, null,
                 Context.RECEIVER_EXPORTED_UNAUDITED);
@@ -121,6 +125,17 @@
         return mBooleanFlagCache.get(id);
     }
 
+    @Override
+    public boolean isEnabled(@NonNull SysPropBooleanFlag flag) {
+        int id = flag.getId();
+        if (!mBooleanFlagCache.containsKey(id)) {
+            mBooleanFlagCache.put(
+                    id, mSystemProperties.getBoolean(flag.getName(), flag.getDefault()));
+        }
+
+        return mBooleanFlagCache.get(id);
+    }
+
     @NonNull
     @Override
     public String getString(@NonNull StringFlag flag) {
@@ -180,16 +195,28 @@
         mSecureSettings.putString(mFlagManager.idToSettingsKey(id), data);
         Log.i(TAG, "Set id " + id + " to " + value);
         removeFromCache(id);
-        mFlagManager.dispatchListenersAndMaybeRestart(id);
+        mFlagManager.dispatchListenersAndMaybeRestart(id, this::restartSystemUI);
+    }
+
+    private <T> void eraseFlag(Flag<T> flag) {
+        if (flag instanceof SysPropFlag) {
+            mSystemProperties.erase(((SysPropFlag<T>) flag).getName());
+            dispatchListenersAndMaybeRestart(flag.getId(), this::restartAndroid);
+        } else {
+            eraseFlag(flag.getId());
+        }
     }
 
     /** Erase a flag's overridden value if there is one. */
-    public void eraseFlag(int id) {
+    private void eraseFlag(int id) {
         eraseInternal(id);
         removeFromCache(id);
-        mFlagManager.dispatchListenersAndMaybeRestart(id);
+        dispatchListenersAndMaybeRestart(id, this::restartSystemUI);
     }
 
+    private void dispatchListenersAndMaybeRestart(int id, Consumer<Boolean> restartAction) {
+        mFlagManager.dispatchListenersAndMaybeRestart(id, restartAction);
+    }
     /** Works just like {@link #eraseFlag(int)} except that it doesn't restart SystemUI. */
     private void eraseInternal(int id) {
         // We can't actually "erase" things from sysprops, but we can set them to empty!
@@ -217,7 +244,11 @@
         System.exit(0);
     }
 
-    private void restartAndroid() {
+    private void restartAndroid(boolean requestSuppress) {
+        if (requestSuppress) {
+            Log.i(TAG, "Android Restart Suppressed");
+            return;
+        }
         Log.i(TAG, "Restarting Android");
         try {
             mBarService.restart();
@@ -273,7 +304,7 @@
             Flag<?> flag = flagMap.get(id);
 
             if (!extras.containsKey(EXTRA_VALUE)) {
-                eraseFlag(id);
+                eraseFlag(flag);
                 return;
             }
 
@@ -282,6 +313,10 @@
                 setFlagValue(id, (Boolean) value, BooleanFlagSerializer.INSTANCE);
             } else  if (flag instanceof ResourceBooleanFlag && value instanceof Boolean) {
                 setFlagValue(id, (Boolean) value, BooleanFlagSerializer.INSTANCE);
+            } else  if (flag instanceof SysPropBooleanFlag && value instanceof Boolean) {
+                // Store SysProp flags in SystemProperties where they can read by outside parties.
+                mSystemProperties.setBoolean(
+                        ((SysPropBooleanFlag) flag).getName(), (Boolean) value);
             } else if (flag instanceof StringFlag && value instanceof String) {
                 setFlagValue(id, (String) value, StringFlagSerializer.INSTANCE);
             } else if (flag instanceof ResourceStringFlag && value instanceof String) {
@@ -306,6 +341,9 @@
             if (f instanceof ResourceBooleanFlag) {
                 return new BooleanFlag(f.getId(), isEnabled((ResourceBooleanFlag) f));
             }
+            if (f instanceof SysPropBooleanFlag) {
+                return new BooleanFlag(f.getId(), isEnabled((SysPropBooleanFlag) f));
+            }
 
             // TODO: add support for other flag types.
             Log.w(TAG, "Unsupported Flag Type. Please file a bug.");
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 348a8e2..1fb1acfa 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -43,11 +43,17 @@
 @SysUISingleton
 public class FeatureFlagsRelease implements FeatureFlags, Dumpable {
     private final Resources mResources;
+    private final SystemPropertiesHelper mSystemProperties;
     SparseBooleanArray mBooleanCache = new SparseBooleanArray();
     SparseArray<String> mStringCache = new SparseArray<>();
+
     @Inject
-    public FeatureFlagsRelease(@Main Resources resources, DumpManager dumpManager) {
+    public FeatureFlagsRelease(
+            @Main Resources resources,
+            SystemPropertiesHelper systemProperties,
+            DumpManager dumpManager) {
         mResources = resources;
+        mSystemProperties = systemProperties;
         dumpManager.registerDumpable("SysUIFlags", this);
     }
 
@@ -72,6 +78,17 @@
         return mBooleanCache.valueAt(cacheIndex);
     }
 
+    @Override
+    public boolean isEnabled(SysPropBooleanFlag flag) {
+        int cacheIndex = mBooleanCache.indexOfKey(flag.getId());
+        if (cacheIndex < 0) {
+            return isEnabled(
+                    flag.getId(), mSystemProperties.getBoolean(flag.getName(), flag.getDefault()));
+        }
+
+        return mBooleanCache.valueAt(cacheIndex);
+    }
+
     private boolean isEnabled(int key, boolean defaultValue) {
         mBooleanCache.append(key, defaultValue);
         return defaultValue;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
index 1dc5a9f..6c16097 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
@@ -34,6 +34,10 @@
         return SystemProperties.getBoolean(name, default)
     }
 
+    fun setBoolean(name: String, value: Boolean) {
+        SystemProperties.set(name, if (value) "1" else "0")
+    }
+
     fun set(name: String, value: String) {
         SystemProperties.set(name, value)
     }
@@ -41,4 +45,8 @@
     fun set(name: String, value: Int) {
         set(name, value.toString())
     }
+
+    fun erase(name: String) {
+        set(name, "")
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 84fa6a6..e3886cd 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -27,7 +27,6 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -117,7 +116,6 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
 import com.android.systemui.scrim.ScrimDrawable;
@@ -125,7 +123,6 @@
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.telephony.TelephonyListenerManager;
@@ -200,7 +197,6 @@
     private final TelecomManager mTelecomManager;
     private final MetricsLogger mMetricsLogger;
     private final UiEventLogger mUiEventLogger;
-    private final SysUiState mSysUiState;
 
     // Used for RingerModeTracker
     private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
@@ -241,7 +237,6 @@
     protected Handler mMainHandler;
     private int mSmallestScreenWidthDp;
     private final Optional<StatusBar> mStatusBarOptional;
-    private final SystemUIDialogManager mDialogManager;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final DialogLaunchAnimator mDialogLaunchAnimator;
 
@@ -347,13 +342,11 @@
             @Background Executor backgroundExecutor,
             UiEventLogger uiEventLogger,
             RingerModeTracker ringerModeTracker,
-            SysUiState sysUiState,
             @Main Handler handler,
             PackageManager packageManager,
             Optional<StatusBar> statusBarOptional,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
-            DialogLaunchAnimator dialogLaunchAnimator,
-            SystemUIDialogManager dialogManager) {
+            DialogLaunchAnimator dialogLaunchAnimator) {
         mContext = context;
         mWindowManagerFuncs = windowManagerFuncs;
         mAudioManager = audioManager;
@@ -379,13 +372,11 @@
         mIWindowManager = iWindowManager;
         mBackgroundExecutor = backgroundExecutor;
         mRingerModeTracker = ringerModeTracker;
-        mSysUiState = sysUiState;
         mMainHandler = handler;
         mSmallestScreenWidthDp = resources.getConfiguration().smallestScreenWidthDp;
         mStatusBarOptional = statusBarOptional;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mDialogLaunchAnimator = dialogLaunchAnimator;
-        mDialogManager = dialogManager;
 
         // receive broadcasts
         IntentFilter filter = new IntentFilter();
@@ -682,11 +673,10 @@
 
         ActionsDialogLite dialog = new ActionsDialogLite(mContext,
                 com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActionsLite,
-                mAdapter, mOverflowAdapter, mSysuiColorExtractor,
-                mStatusBarService, mNotificationShadeWindowController,
-                mSysUiState, this::onRefresh, mKeyguardShowing, mPowerAdapter, mUiEventLogger,
-                mStatusBarOptional, mKeyguardUpdateMonitor, mLockPatternUtils,
-                mDialogManager);
+                mAdapter, mOverflowAdapter, mSysuiColorExtractor, mStatusBarService,
+                mNotificationShadeWindowController, this::onRefresh, mKeyguardShowing,
+                mPowerAdapter, mUiEventLogger, mStatusBarOptional, mKeyguardUpdateMonitor,
+                mLockPatternUtils);
 
         dialog.setOnDismissListener(this);
         dialog.setOnShowListener(this);
@@ -2165,7 +2155,6 @@
         private boolean mKeyguardShowing;
         protected float mScrimAlpha;
         protected final NotificationShadeWindowController mNotificationShadeWindowController;
-        protected final SysUiState mSysUiState;
         private ListPopupWindow mOverflowPopup;
         private Dialog mPowerOptionsDialog;
         protected final Runnable mOnRefreshCallback;
@@ -2226,15 +2215,13 @@
                 MyOverflowAdapter overflowAdapter,
                 SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
                 NotificationShadeWindowController notificationShadeWindowController,
-                SysUiState sysuiState, Runnable onRefreshCallback, boolean keyguardShowing,
+                Runnable onRefreshCallback, boolean keyguardShowing,
                 MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
-                Optional<StatusBar> statusBarOptional,
-                KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils,
-                SystemUIDialogManager systemUiDialogManager) {
+                Optional<StatusBar> statusBarOptional, KeyguardUpdateMonitor keyguardUpdateMonitor,
+                LockPatternUtils lockPatternUtils) {
             // We set dismissOnDeviceLock to false because we have a custom broadcast receiver to
             // dismiss this dialog when the device is locked.
-            super(context, themeRes, false /* dismissOnDeviceLock */,
-                    systemUiDialogManager);
+            super(context, themeRes, false /* dismissOnDeviceLock */);
             mContext = context;
             mAdapter = adapter;
             mOverflowAdapter = overflowAdapter;
@@ -2242,7 +2229,6 @@
             mColorExtractor = sysuiColorExtractor;
             mStatusBarService = statusBarService;
             mNotificationShadeWindowController = notificationShadeWindowController;
-            mSysUiState = sysuiState;
             mOnRefreshCallback = onRefreshCallback;
             mKeyguardShowing = keyguardShowing;
             mUiEventLogger = uiEventLogger;
@@ -2463,8 +2449,6 @@
         public void show() {
             super.show();
             mNotificationShadeWindowController.setRequestTopUi(true, TAG);
-            mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, true)
-                    .commitUpdate(mContext.getDisplayId());
 
             // By default this dialog windowAnimationStyle is null, and therefore windowAnimations
             // should be equal to 0 which means we need to animate the dialog in-window. If it's not
@@ -2563,9 +2547,6 @@
             dismissPowerOptions();
 
             mNotificationShadeWindowController.setRequestTopUi(false, TAG);
-            mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, false)
-                    .commitUpdate(mContext.getDisplayId());
-
             super.dismiss();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index eee3955..f4b6fbd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -795,6 +795,9 @@
     @TransformationType
     fun calculateTransformationType(): Int {
         if (isTransitioningToFullShade) {
+            if (inSplitShade) {
+                return TRANSFORMATION_TYPE_TRANSITION
+            }
             return TRANSFORMATION_TYPE_FADE
         }
         if (previousLocation == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QS ||
@@ -961,6 +964,7 @@
             (qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
             qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
             !hasActiveMedia -> LOCATION_QS
+            onLockscreen && isSplitShadeExpanding() -> LOCATION_QS
             onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
             onLockscreen && allowedOnLockscreen -> LOCATION_LOCKSCREEN
             else -> LOCATION_QQS
@@ -986,6 +990,10 @@
         return location
     }
 
+    private fun isSplitShadeExpanding(): Boolean {
+        return inSplitShade && isTransitioningToFullShade
+    }
+
     /**
      * Are we currently transforming to the full shade and already in QQS
      */
@@ -993,6 +1001,10 @@
         if (!isTransitioningToFullShade) {
             return false
         }
+        if (inSplitShade) {
+            // Split shade doesn't use QQS.
+            return false
+        }
         return fullShadeTransitionProgress > 0.5f
     }
 
@@ -1000,6 +1012,10 @@
      * Is the current transformationType fading
      */
     private fun isCurrentlyFading(): Boolean {
+        if (isSplitShadeExpanding()) {
+            // Split shade always uses transition instead of fade.
+            return false
+        }
         if (isTransitioningToFullShade) {
             return true
         }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 1f11d0c..c96aca3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -249,7 +249,7 @@
             mSeekBar.setMin(0);
             final int currentVolume = device.getCurrentVolume();
             if (mSeekBar.getProgress() != currentVolume) {
-                mSeekBar.setProgress(currentVolume);
+                mSeekBar.setProgress(currentVolume, true);
             }
             mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
                 @Override
@@ -278,7 +278,7 @@
             mSeekBar.setMin(0);
             final int currentVolume = mController.getSessionVolume();
             if (mSeekBar.getProgress() != currentVolume) {
-                mSeekBar.setProgress(currentVolume);
+                mSeekBar.setProgress(currentVolume, true);
             }
             mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
                 @Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 7bb5454..04a324b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -56,7 +56,6 @@
 
 import com.android.systemui.R;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 /**
  * Base dialog for media output UI
@@ -99,9 +98,8 @@
         }
     };
 
-    public MediaOutputBaseDialog(Context context, MediaOutputController mediaOutputController,
-            SystemUIDialogManager dialogManager) {
-        super(context, R.style.Theme_SystemUI_Dialog_Media, dialogManager);
+    public MediaOutputBaseDialog(Context context, MediaOutputController mediaOutputController) {
+        super(context, R.style.Theme_SystemUI_Dialog_Media);
 
         // Save the context that is wrapped with our theme.
         mContext = getContext();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 7bc0f52..f16ca7d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -66,7 +66,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -91,10 +90,8 @@
     private final ShadeController mShadeController;
     private final ActivityStarter mActivityStarter;
     private final DialogLaunchAnimator mDialogLaunchAnimator;
-    private final SystemUIDialogManager mDialogManager;
     private final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>();
     private final boolean mAboveStatusbar;
-    private final boolean mVolumeAdjustmentForRemoteGroupSessions;
     private final CommonNotifCollection mNotifCollection;
     @VisibleForTesting
     final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
@@ -119,7 +116,7 @@
             boolean aboveStatusbar, MediaSessionManager mediaSessionManager, LocalBluetoothManager
             lbm, ShadeController shadeController, ActivityStarter starter,
             CommonNotifCollection notifCollection, UiEventLogger uiEventLogger,
-            DialogLaunchAnimator dialogLaunchAnimator, SystemUIDialogManager dialogManager) {
+            DialogLaunchAnimator dialogLaunchAnimator) {
         mContext = context;
         mPackageName = packageName;
         mMediaSessionManager = mediaSessionManager;
@@ -133,9 +130,6 @@
         mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
         mUiEventLogger = uiEventLogger;
         mDialogLaunchAnimator = dialogLaunchAnimator;
-        mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
-        mDialogManager = dialogManager;
         mColorActiveItem = Utils.getColorStateListDefaultColor(mContext,
                 R.color.media_dialog_active_item_main_content);
         mColorInactiveItem = Utils.getColorStateListDefaultColor(mContext,
@@ -610,10 +604,9 @@
         // We show the output group dialog from the output dialog.
         MediaOutputController controller = new MediaOutputController(mContext, mPackageName,
                 mAboveStatusbar, mMediaSessionManager, mLocalBluetoothManager, mShadeController,
-                mActivityStarter, mNotifCollection, mUiEventLogger, mDialogLaunchAnimator,
-                mDialogManager);
+                mActivityStarter, mNotifCollection, mUiEventLogger, mDialogLaunchAnimator);
         MediaOutputGroupDialog dialog = new MediaOutputGroupDialog(mContext, mAboveStatusbar,
-                controller, mDialogManager);
+                controller);
         mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog);
     }
 
@@ -625,10 +618,15 @@
                 || features.contains(MediaRoute2Info.FEATURE_REMOTE_GROUP_PLAYBACK));
     }
 
+    private boolean isPlayBackInfoLocal() {
+        return mMediaController.getPlaybackInfo() != null
+                && mMediaController.getPlaybackInfo().getPlaybackType()
+                == MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+    }
+
     boolean isVolumeControlEnabled(@NonNull MediaDevice device) {
-        // TODO(b/202500642): Also enable volume control for remote non-group sessions.
-        return !isActiveRemoteDevice(device)
-            || mVolumeAdjustmentForRemoteGroupSessions;
+        return isPlayBackInfoLocal()
+                || mLocalMediaManager.isMediaSessionAvailableForVolumeControl();
     }
 
     private final MediaController.Callback mCb = new MediaController.Callback() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index 4e9da55..7696a1f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -29,7 +29,6 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 /**
  * Dialog for media output transferring.
@@ -39,9 +38,8 @@
     final UiEventLogger mUiEventLogger;
 
     MediaOutputDialog(Context context, boolean aboveStatusbar, MediaOutputController
-            mediaOutputController, UiEventLogger uiEventLogger,
-            SystemUIDialogManager dialogManager) {
-        super(context, mediaOutputController, dialogManager);
+            mediaOutputController, UiEventLogger uiEventLogger) {
+        super(context, mediaOutputController);
         mUiEventLogger = uiEventLogger;
         mAdapter = new MediaOutputAdapter(mMediaOutputController, this);
         if (!aboveStatusbar) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index e1e7fa3..9e252ea 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.phone.ShadeController
-import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import javax.inject.Inject
 
 /**
@@ -39,8 +38,7 @@
     private val starter: ActivityStarter,
     private val notifCollection: CommonNotifCollection,
     private val uiEventLogger: UiEventLogger,
-    private val dialogLaunchAnimator: DialogLaunchAnimator,
-    private val dialogManager: SystemUIDialogManager
+    private val dialogLaunchAnimator: DialogLaunchAnimator
 ) {
     companion object {
         var mediaOutputDialog: MediaOutputDialog? = null
@@ -53,9 +51,8 @@
 
         val controller = MediaOutputController(context, packageName, aboveStatusBar,
             mediaSessionManager, lbm, shadeController, starter, notifCollection,
-            uiEventLogger, dialogLaunchAnimator, dialogManager)
-        val dialog = MediaOutputDialog(context, aboveStatusBar, controller, uiEventLogger,
-                dialogManager)
+            uiEventLogger, dialogLaunchAnimator)
+        val dialog = MediaOutputDialog(context, aboveStatusBar, controller, uiEventLogger)
         mediaOutputDialog = dialog
 
         // Show the dialog.
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java
index 9f752b9..f1c6601 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java
@@ -25,7 +25,6 @@
 import androidx.core.graphics.drawable.IconCompat;
 
 import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 /**
  * Dialog for media output group.
@@ -34,8 +33,8 @@
 public class MediaOutputGroupDialog extends MediaOutputBaseDialog {
 
     MediaOutputGroupDialog(Context context, boolean aboveStatusbar, MediaOutputController
-            mediaOutputController, SystemUIDialogManager dialogManager) {
-        super(context, mediaOutputController, dialogManager);
+            mediaOutputController) {
+        super(context, mediaOutputController);
         mMediaOutputController.resetGroupMediaDevices();
         mAdapter = new MediaOutputGroupAdapter(mMediaOutputController);
         if (!aboveStatusbar) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index ee2fba0..6ec2b6e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -29,6 +29,7 @@
 import com.android.internal.widget.CachingIconView
 import com.android.systemui.R
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.gesture.TapGestureDetector
 import com.android.systemui.util.concurrency.DelayableExecutor
 
 /**
@@ -42,6 +43,7 @@
     internal val context: Context,
     private val windowManager: WindowManager,
     @Main private val mainExecutor: DelayableExecutor,
+    private val tapGestureDetector: TapGestureDetector,
     @LayoutRes private val chipLayoutRes: Int
 ) {
     /** The window layout parameters we'll use when attaching the view to a window. */
@@ -82,6 +84,7 @@
 
         // Add view if necessary
         if (oldChipView == null) {
+            tapGestureDetector.addOnGestureDetectedCallback(TAG, this::removeChip)
             windowManager.addView(chipView, windowLayoutParams)
         }
 
@@ -96,6 +99,7 @@
         //  TransferTriggered state: Once the user has initiated the transfer, they should be able
         //  to move away from the receiver device but still see the status of the transfer.
         if (chipView == null) { return }
+        tapGestureDetector.removeOnGestureDetectedCallback(TAG)
         windowManager.removeView(chipView)
         chipView = null
     }
@@ -128,5 +132,6 @@
 // Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and
 // UpdateMediaTapToTransferReceiverDisplayTest
 private const val WINDOW_TITLE = "Media Transfer Chip View"
+private val TAG = MediaTttChipControllerCommon::class.simpleName!!
 @VisibleForTesting
 const val TIMEOUT_MILLIS = 3000L
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 214a888..b6f1aea 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon
 import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.gesture.TapGestureDetector
 import com.android.systemui.util.concurrency.DelayableExecutor
 import javax.inject.Inject
 
@@ -43,9 +44,10 @@
     context: Context,
     windowManager: WindowManager,
     mainExecutor: DelayableExecutor,
+    tapGestureDetector: TapGestureDetector,
     @Main private val mainHandler: Handler,
 ) : MediaTttChipControllerCommon<ChipStateReceiver>(
-    context, windowManager, mainExecutor, R.layout.media_ttt_chip_receiver
+    context, windowManager, mainExecutor, tapGestureDetector, R.layout.media_ttt_chip_receiver
 ) {
     private val commandQueueCallbacks = object : CommandQueue.Callbacks {
         override fun updateMediaTapToTransferReceiverDisplay(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index 482e604..fef17fdc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon
 import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.gesture.TapGestureDetector
 import com.android.systemui.util.concurrency.DelayableExecutor
 import javax.inject.Inject
 
@@ -42,9 +43,10 @@
     commandQueue: CommandQueue,
     context: Context,
     windowManager: WindowManager,
-    @Main private val mainExecutor: DelayableExecutor,
+    @Main mainExecutor: DelayableExecutor,
+    tapGestureDetector: TapGestureDetector,
 ) : MediaTttChipControllerCommon<ChipStateSender>(
-    context, windowManager, mainExecutor,  R.layout.media_ttt_chip
+    context, windowManager, mainExecutor,  tapGestureDetector, R.layout.media_ttt_chip
 ) {
     private val commandQueueCallbacks = object : CommandQueue.Callbacks {
         override fun updateMediaTapToTransferSenderDisplay(
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 8b39e5c..38e21f2 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -440,7 +440,9 @@
                         mHomeButtonLongPressDurationMs = Optional.of(
                             properties.getLong(HOME_BUTTON_LONG_PRESS_DURATION_MS, 0)
                         ).filter(duration -> duration != 0);
-                        reconfigureHomeLongClick();
+                        if (mNavigationBarView != null) {
+                            reconfigureHomeLongClick();
+                        }
                     }
                 }
             };
@@ -761,7 +763,8 @@
                         | WindowManager.LayoutParams.FLAG_SLIPPERY,
                 PixelFormat.TRANSLUCENT);
         mOrientationParams.setTitle("SecondaryHomeHandle" + mContext.getDisplayId());
-        mOrientationParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
+        mOrientationParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION
+                | WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
         mWindowManager.addView(mOrientationHandle, mOrientationParams);
         mOrientationHandle.setVisibility(View.GONE);
         mOrientationParams.setFitInsetsTypes(0 /* types*/);
@@ -1588,7 +1591,8 @@
         }
         lp.token = new Binder();
         lp.accessibilityTitle = mContext.getString(R.string.nav_bar);
-        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
+        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC
+                | WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
         lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         lp.windowAnimations = 0;
         lp.setTitle("NavigationBar" + mContext.getDisplayId());
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 9ea2763..371e4f4 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -936,6 +936,9 @@
 
     public void setBackAnimation(BackAnimation backAnimation) {
         mBackAnimation = backAnimation;
+        if (mEdgeBackPlugin != null && mEdgeBackPlugin instanceof NavigationBarEdgePanel) {
+            ((NavigationBarEdgePanel) mEdgeBackPlugin).setBackAnimation(backAnimation);
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index a6bad15..a6919e8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -280,7 +280,7 @@
                 }
             };
     private BackCallback mBackCallback;
-    private final BackAnimation mBackAnimation;
+    private BackAnimation mBackAnimation;
 
     public NavigationBarEdgePanel(Context context,
             BackAnimation backAnimation) {
@@ -385,6 +385,10 @@
         mShowProtection = !isPrimaryDisplay;
     }
 
+    public void setBackAnimation(BackAnimation backAnimation) {
+        mBackAnimation = backAnimation;
+    }
+
     @Override
     public void onDestroy() {
         cancelFailsafe();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index a262b8a..217210a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -165,6 +165,7 @@
             powerMenuLite.visibility = View.GONE
         }
         settingsButton.setOnClickListener(onClickListener)
+        multiUserSetting.isListening = true
         if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
             val securityFooter = securityFooterController.view as DualHeightHorizontalLinearLayout
             securityFootersContainer?.addView(securityFooter)
@@ -215,6 +216,7 @@
 
     override fun onViewDetached() {
         setListening(false)
+        multiUserSetting.isListening = false
     }
 
     fun setListening(listening: Boolean) {
@@ -222,7 +224,6 @@
             return
         }
         this.listening = listening
-        multiUserSetting.isListening = listening
         if (this.listening) {
             userInfoController.addCallback(onUserInfoChangedListener)
             updateView()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 3c7933f..3ef7220 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -513,7 +513,8 @@
         mContainer.setExpansion(expansion);
         final float translationScaleY = (mInSplitShade
                 ? 1 : QSAnimator.SHORT_PARALLAX_AMOUNT) * (expansion - 1);
-        boolean onKeyguardAndExpanded = isKeyguardState() && !mShowCollapsedOnKeyguard;
+        boolean onKeyguard = isKeyguardState();
+        boolean onKeyguardAndExpanded = onKeyguard && !mShowCollapsedOnKeyguard;
         if (!mHeaderAnimating && !headerWillBeAnimating()) {
             getView().setTranslationY(
                     onKeyguardAndExpanded
@@ -547,6 +548,7 @@
                 mHeader.updateResources();
             }
         }
+        mQSPanelController.setIsOnKeyguard(onKeyguard);
         mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
         mQSFooterActionController.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
         mQSPanelController.setRevealExpansion(expansion);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 5126fcb..b04d752 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -110,6 +110,8 @@
     private final ArrayMap<View, Integer> mChildrenLayoutTop = new ArrayMap<>();
     private final Rect mClippingRect = new Rect();
     private boolean mUseNewFooter = false;
+    private ViewGroup mMediaHostView;
+    private boolean mShouldMoveMediaOnExpansion = true;
 
     public QSPanel(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -289,9 +291,15 @@
         for (int i = 0; i < getChildCount(); i++) {
             View child = getChildAt(i);
             if (move) {
+                int topOffset;
+                if (child == mMediaHostView && !mShouldMoveMediaOnExpansion) {
+                    topOffset = 0;
+                } else {
+                    topOffset = tileHeightOffset;
+                }
                 int top = Objects.requireNonNull(mChildrenLayoutTop.get(child));
-                child.setLeftTopRightBottom(child.getLeft(), top + tileHeightOffset,
-                        child.getRight(), top + tileHeightOffset + child.getHeight());
+                child.setLeftTopRightBottom(child.getLeft(), top + topOffset,
+                        child.getRight(), top + topOffset + child.getHeight());
             }
             if (child == mTileLayout) {
                 move = true;
@@ -463,6 +471,7 @@
         if (!mUsingMediaPlayer) {
             return;
         }
+        mMediaHostView = hostView;
         ViewGroup newParent = horizontal ? mHorizontalLinearLayout : this;
         ViewGroup currentParent = (ViewGroup) hostView.getParent();
         if (currentParent != newParent) {
@@ -656,6 +665,19 @@
         updatePadding();
     }
 
+    /**
+     * Sets whether the media container should move during the expansion of the QS Panel.
+     *
+     * As the QS Panel expands and the QS unsquish, the views below the QS tiles move to adapt to
+     * the new height of the QS tiles.
+     *
+     * In some cases this might not be wanted for media. One example is when there is a transition
+     * animation of the media container happening on split shade lock screen.
+     */
+    public void setShouldMoveMediaOnExpansion(boolean shouldMoveMediaOnExpansion) {
+        mShouldMoveMediaOnExpansion = shouldMoveMediaOnExpansion;
+    }
+
     private class H extends Handler {
         private static final int ANNOUNCE_FOR_ACCESSIBILITY = 1;
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 3172aa9..6572daa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -419,6 +419,16 @@
         return mView.getBrightnessView();
     }
 
+    /** Sets whether we are currently on lock screen. */
+    public void setIsOnKeyguard(boolean isOnKeyguard) {
+        boolean isOnSplitShadeLockscreen = mShouldUseSplitNotificationShade && isOnKeyguard;
+        // When the split shade is expanding on lockscreen, the media container transitions from the
+        // lockscreen to QS.
+        // We have to prevent the media container position from moving during the transition to have
+        // a smooth translation animation without stuttering.
+        mView.setShouldMoveMediaOnExpansion(!isOnSplitShadeLockscreen);
+    }
+
     /** */
     public static final class TileRecord {
         public TileRecord(QSTile tile, com.android.systemui.plugins.qs.QSTileView tileView) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 131589f..999818f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -116,7 +116,8 @@
 
     private String mTileSpec;
     @Nullable
-    private EnforcedAdmin mEnforcedAdmin;
+    @VisibleForTesting
+    protected EnforcedAdmin mEnforcedAdmin;
     private boolean mShowingDetail;
     private int mIsFullQs;
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index c61c18a..f736231 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -24,6 +24,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.service.quicksettings.Tile;
 import android.text.TextUtils;
@@ -114,6 +115,7 @@
 
     @Override
     protected void handleUpdateState(BooleanState state, Object arg) {
+        checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_BLUETOOTH);
         final boolean transientEnabling = arg == ARG_SHOW_TRANSIENT_ENABLING;
         final boolean enabled = transientEnabling || mController.isBluetoothEnabled();
         final boolean connected = mController.isBluetoothConnected();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
index e1d2070..8bad2de 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
@@ -47,9 +47,6 @@
 public class InternetAdapter extends RecyclerView.Adapter<InternetAdapter.InternetViewHolder> {
 
     private static final String TAG = "InternetAdapter";
-    private static final String ACTION_WIFI_DIALOG = "com.android.settings.WIFI_DIALOG";
-    private static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key";
-    private static final String EXTRA_CONNECT_FOR_CALLER = "connect_for_caller";
 
     private final InternetDialogController mInternetDialogController;
     @Nullable
@@ -169,11 +166,10 @@
             }
             mWifiListLayout.setOnClickListener(v -> {
                 if (wifiEntry.shouldEditBeforeConnect()) {
-                    final Intent intent = new Intent(ACTION_WIFI_DIALOG);
+                    final Intent intent = WifiUtils.getWifiDialogIntent(wifiEntry.getKey(),
+                            true /* connectForCaller */);
                     intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                     intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
-                    intent.putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, wifiEntry.getKey());
-                    intent.putExtra(EXTRA_CONNECT_FOR_CALLER, false);
                     mContext.startActivity(intent);
                 }
                 mInternetDialogController.connect(wifiEntry);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index b3bc3be..b322cbf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -112,7 +112,6 @@
             "android.settings.NETWORK_PROVIDER_SETTINGS";
     private static final String ACTION_WIFI_SCANNING_SETTINGS =
             "android.settings.WIFI_SCANNING_SETTINGS";
-    private static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key";
     public static final Drawable EMPTY_DRAWABLE = new ColorDrawable(Color.TRANSPARENT);
     public static final int NO_CELL_DATA_TYPE_ICON = 0;
     private static final int SUBTITLE_TEXT_WIFI_IS_OFF = R.string.wifi_is_off;
@@ -853,8 +852,8 @@
             }
 
             if (status == WifiEntry.ConnectCallback.CONNECT_STATUS_FAILURE_NO_CONFIG) {
-                final Intent intent = new Intent("com.android.settings.WIFI_DIALOG")
-                        .putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, mWifiEntry.getKey());
+                final Intent intent = WifiUtils.getWifiDialogIntent(mWifiEntry.getKey(),
+                        true /* connectForCaller */);
                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                 mActivityStarter.startActivity(intent, false /* dismissShade */);
             } else if (status == CONNECT_STATUS_FAILURE_UNKNOWN) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 4a9a1f1..6d729b9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -403,12 +403,24 @@
                     }
 
                     @Override
-                    public void onDismiss() {
+                    public void onSwipeDismissInitiated(Animator anim) {
                         if (DEBUG_DISMISS) {
                             Log.d(ScreenshotView.TAG, "dismiss triggered via swipe gesture");
                         }
                         mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, 0,
                                 mPackageName);
+                        anim.addListener(new AnimatorListenerAdapter() {
+                            @Override
+                            public void onAnimationStart(Animator animation) {
+                                super.onAnimationStart(animation);
+                                mBackgroundProtection.animate()
+                                        .alpha(0).setDuration(anim.getDuration()).start();
+                            }
+                        });
+                    }
+
+                    @Override
+                    public void onDismissComplete() {
                         mCallbacks.onDismiss();
                     }
                 });
@@ -995,6 +1007,7 @@
         mSmartChips.clear();
         mQuickShareChip = null;
         setAlpha(1);
+        mScreenshotStatic.setAlpha(1);
         mScreenshotSelectorView.stop();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java b/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java
index 451fb13..24b1249 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java
@@ -49,9 +49,14 @@
         void onInteraction();
 
         /**
+         * Run when the view is dismissed (the distance threshold is met), pre-dismissal animation
+         */
+        void onSwipeDismissInitiated(Animator animator);
+
+        /**
          * Run when the view is dismissed (the distance threshold is met), post-dismissal animation
          */
-        void onDismiss();
+        void onDismissComplete();
     }
 
     private final View mView;
@@ -89,7 +94,9 @@
                 return true;
             }
             if (isPastDismissThreshold()) {
-                dismiss();
+                ValueAnimator dismissAnimator = createSwipeDismissAnimation(1);
+                mCallbacks.onSwipeDismissInitiated(dismissAnimator);
+                dismiss(dismissAnimator);
             } else {
                 // if we've moved, but not past the threshold, start the return animation
                 if (DEBUG_DISMISS) {
@@ -117,7 +124,10 @@
                 float velocityY) {
             if (mView.getTranslationX() * velocityX > 0
                     && (mDismissAnimation == null || !mDismissAnimation.isRunning())) {
-                dismiss(velocityX / (float) 1000);
+                ValueAnimator dismissAnimator =
+                        createSwipeDismissAnimation(velocityX / (float) 1000);
+                mCallbacks.onSwipeDismissInitiated(dismissAnimator);
+                dismiss(dismissAnimator);
                 return true;
             }
             return false;
@@ -160,11 +170,11 @@
      * Start dismissal animation (will run onDismiss callback when animation complete)
      */
     public void dismiss() {
-        dismiss(1);
+        dismiss(createSwipeDismissAnimation(1));
     }
 
-    private void dismiss(float velocity) {
-        mDismissAnimation = createSwipeDismissAnimation(velocity);
+    private void dismiss(ValueAnimator animator) {
+        mDismissAnimation = animator;
         mDismissAnimation.addListener(new AnimatorListenerAdapter() {
             private boolean mCancelled;
 
@@ -178,7 +188,7 @@
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
                 if (!mCancelled) {
-                    mCallbacks.onDismiss();
+                    mCallbacks.onDismissComplete();
                 }
             }
         });
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 8366bdd..8a9d6dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -342,9 +342,7 @@
                     qS.setTransitionToFullShadeAmount(field, qSDragProgress)
                     notificationPanelController.setTransitionToFullShadeAmount(field,
                             false /* animate */, 0 /* delay */)
-                    // TODO: appear media also in split shade
-                    val mediaAmount = if (useSplitShade) 0f else field
-                    mediaHierarchyManager.setTransitionToFullShadeAmount(mediaAmount)
+                    mediaHierarchyManager.setTransitionToFullShadeAmount(field)
                     transitionToShadeAmountCommon(field)
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 092e86d..3f101bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -45,7 +45,6 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
 import com.android.systemui.statusbar.notification.collection.render.NotifStats;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -97,7 +96,6 @@
     private final Optional<Bubbles> mBubblesOptional;
     private final DynamicPrivacyController mDynamicPrivacyController;
     private final KeyguardBypassController mBypassController;
-    private final ForegroundServiceSectionController mFgsSectionController;
     private final NotifPipelineFlags mNotifPipelineFlags;
     private AssistantFeedbackController mAssistantFeedbackController;
     private final KeyguardStateController mKeyguardStateController;
@@ -129,7 +127,6 @@
             KeyguardBypassController bypassController,
             Optional<Bubbles> bubblesOptional,
             DynamicPrivacyController privacyController,
-            ForegroundServiceSectionController fgsSectionController,
             DynamicChildBindController dynamicChildBindController,
             LowPriorityInflationHelper lowPriorityInflationHelper,
             AssistantFeedbackController assistantFeedbackController,
@@ -145,7 +142,6 @@
         mVisualStabilityManager = visualStabilityManager;
         mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
         mEntryManager = notificationEntryManager;
-        mFgsSectionController = fgsSectionController;
         mNotifPipelineFlags = notifPipelineFlags;
         Resources res = context.getResources();
         mAlwaysExpandNonGroupedNotification =
@@ -417,8 +413,7 @@
                 && mBubblesOptional.get().isBubbleNotificationSuppressedFromShade(
                         ent.getKey(), ent.getSbn().getGroupKey());
         if (ent.isRowDismissed() || ent.isRowRemoved()
-                || isBubbleNotificationSuppressedFromShade
-                || mFgsSectionController.hasEntry(ent)) {
+                || isBubbleNotificationSuppressedFromShade) {
             // we want to suppress removed notifications because they could
             // temporarily become children if they were isolated before.
             return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index c687e82..364f36a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -60,7 +60,6 @@
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
@@ -198,7 +197,6 @@
             KeyguardBypassController bypassController,
             Optional<Bubbles> bubblesOptional,
             DynamicPrivacyController privacyController,
-            ForegroundServiceSectionController fgsSectionController,
             DynamicChildBindController dynamicChildBindController,
             LowPriorityInflationHelper lowPriorityInflationHelper,
             AssistantFeedbackController assistantFeedbackController,
@@ -217,7 +215,6 @@
                 bypassController,
                 bubblesOptional,
                 privacyController,
-                fgsSectionController,
                 dynamicChildBindController,
                 lowPriorityInflationHelper,
                 assistantFeedbackController,
@@ -261,6 +258,7 @@
     @Provides
     @SysUISingleton
     static OngoingCallController provideOngoingCallController(
+            Context context,
             CommonNotifCollection notifCollection,
             SystemClock systemClock,
             ActivityStarter activityStarter,
@@ -284,6 +282,7 @@
                         : Optional.empty();
         OngoingCallController ongoingCallController =
                 new OngoingCallController(
+                        context,
                         notifCollection,
                         ongoingCallFlags,
                         systemClock,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt
new file mode 100644
index 0000000..76766b0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.systemui.statusbar.gesture
+
+import android.annotation.CallSuper
+import android.os.Looper
+import android.view.Choreographer
+import android.view.Display
+import android.view.InputEvent
+import com.android.systemui.shared.system.InputChannelCompat
+import com.android.systemui.shared.system.InputMonitorCompat
+
+/**
+ * An abstract class to help detect gestures that occur anywhere on the display (not specific to a
+ * certain view).
+ *
+ * This class handles starting/stopping the gesture detection system as well as
+ * registering/unregistering callbacks for when gestures occur. Note that the class will only listen
+ * for gestures when there's at least one callback registered.
+ *
+ * Subclasses should implement [onInputEvent] to detect their specific gesture. Once a specific
+ * gesture is detected, they should call [onGestureDetected] (which will notify the callbacks).
+ */
+abstract class GenericGestureDetector(
+    private val tag: String
+) {
+    /**
+     * Active callbacks, each associated with a tag. Gestures will only be monitored if
+     * [callbacks.size] > 0.
+     */
+    private val callbacks: MutableMap<String, () -> Unit> = mutableMapOf()
+
+    private var inputMonitor: InputMonitorCompat? = null
+    private var inputReceiver: InputChannelCompat.InputEventReceiver? = null
+
+    /** Adds a callback that will be triggered when the tap gesture is detected. */
+    fun addOnGestureDetectedCallback(tag: String, callback: () -> Unit) {
+        val callbacksWasEmpty = callbacks.isEmpty()
+        callbacks[tag] = callback
+        if (callbacksWasEmpty) {
+            startGestureListening()
+        }
+    }
+
+    /** Removes the callback. */
+    fun removeOnGestureDetectedCallback(tag: String) {
+        callbacks.remove(tag)
+        if (callbacks.isEmpty()) {
+            stopGestureListening()
+        }
+    }
+
+    /** Triggered each time a touch event occurs (and at least one callback is registered). */
+    abstract fun onInputEvent(ev: InputEvent)
+
+    /** Should be called by subclasses when their specific gesture is detected. */
+    internal fun onGestureDetected() {
+        callbacks.values.forEach { it.invoke() }
+    }
+
+    /** Start listening to touch events. */
+    @CallSuper
+    internal open fun startGestureListening() {
+        stopGestureListening()
+
+        inputMonitor = InputMonitorCompat(tag, Display.DEFAULT_DISPLAY).also {
+            inputReceiver = it.getInputReceiver(
+                Looper.getMainLooper(),
+                Choreographer.getInstance(),
+                this::onInputEvent
+            )
+        }
+    }
+
+    /** Stop listening to touch events. */
+    @CallSuper
+    internal open fun stopGestureListening() {
+        inputMonitor?.let {
+            inputMonitor = null
+            it.dispose()
+        }
+        inputReceiver?.let {
+            inputReceiver = null
+            it.dispose()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
index 7cdf69d..fcb285a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
@@ -17,15 +17,13 @@
 package com.android.systemui.statusbar.gesture
 
 import android.content.Context
-import android.os.Looper
-import android.view.Choreographer
-import android.view.Display
 import android.view.InputEvent
 import android.view.MotionEvent
-import android.view.MotionEvent.*
+import android.view.MotionEvent.ACTION_CANCEL
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_MOVE
+import android.view.MotionEvent.ACTION_UP
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.shared.system.InputChannelCompat
-import com.android.systemui.shared.system.InputMonitorCompat
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import javax.inject.Inject
 
@@ -38,43 +36,17 @@
     context: Context,
     private val statusBarWindowController: StatusBarWindowController,
     private val logger: SwipeStatusBarAwayGestureLogger
-) {
-
-    /**
-     * Active callbacks, each associated with a tag. Gestures will only be monitored if
-     * [callbacks.size] > 0.
-     */
-    private val callbacks: MutableMap<String, () -> Unit> = mutableMapOf()
+) : GenericGestureDetector(SwipeStatusBarAwayGestureHandler::class.simpleName!!) {
 
     private var startY: Float = 0f
     private var startTime: Long = 0L
     private var monitoringCurrentTouch: Boolean = false
 
-    private var inputMonitor: InputMonitorCompat? = null
-    private var inputReceiver: InputChannelCompat.InputEventReceiver? = null
-
     private var swipeDistanceThreshold: Int = context.resources.getDimensionPixelSize(
         com.android.internal.R.dimen.system_gestures_start_threshold
     )
 
-    /** Adds a callback that will be triggered when the swipe away gesture is detected. */
-    fun addOnGestureDetectedCallback(tag: String, callback: () -> Unit) {
-        val callbacksWasEmpty = callbacks.isEmpty()
-        callbacks[tag] = callback
-        if (callbacksWasEmpty) {
-            startGestureListening()
-        }
-    }
-
-    /** Removes the callback. */
-    fun removeOnGestureDetectedCallback(tag: String) {
-        callbacks.remove(tag)
-        if (callbacks.isEmpty()) {
-             stopGestureListening()
-        }
-    }
-
-    private fun onInputEvent(ev: InputEvent) {
+    override fun onInputEvent(ev: InputEvent) {
         if (ev !is MotionEvent) {
             return
         }
@@ -108,7 +80,7 @@
                 ) {
                     monitoringCurrentTouch = false
                     logger.logGestureDetected(ev.y.toInt())
-                    callbacks.values.forEach { it.invoke() }
+                    onGestureDetected()
                 }
             }
             ACTION_CANCEL, ACTION_UP -> {
@@ -120,33 +92,15 @@
         }
     }
 
-    /** Start listening for the swipe gesture. */
-    private fun startGestureListening() {
-        stopGestureListening()
-
+    override fun startGestureListening() {
+        super.startGestureListening()
         logger.logInputListeningStarted()
-        inputMonitor = InputMonitorCompat(TAG, Display.DEFAULT_DISPLAY).also {
-            inputReceiver = it.getInputReceiver(
-                Looper.getMainLooper(),
-                Choreographer.getInstance(),
-                this::onInputEvent
-            )
-        }
     }
 
-    /** Stop listening for the swipe gesture. */
-    private fun stopGestureListening() {
-        inputMonitor?.let {
-            logger.logInputListeningStopped()
-            inputMonitor = null
-            it.dispose()
-        }
-        inputReceiver?.let {
-            inputReceiver = null
-            it.dispose()
-        }
+    override fun stopGestureListening() {
+        super.stopGestureListening()
+        logger.logInputListeningStopped()
     }
 }
 
 private const val SWIPE_TIMEOUT_MS: Long = 500
-private val TAG = SwipeStatusBarAwayGestureHandler::class.simpleName
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt
new file mode 100644
index 0000000..4107ce2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.gesture
+
+import android.content.Context
+import android.view.GestureDetector
+import android.view.InputEvent
+import android.view.MotionEvent
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/**
+ * A class to detect when a user taps the screen. To be notified when the tap is detected, add a
+ * callback via [addOnGestureDetectedCallback].
+ */
+@SysUISingleton
+class TapGestureDetector @Inject constructor(
+    private val context: Context
+) : GenericGestureDetector(TapGestureDetector::class.simpleName!!) {
+
+    private val gestureListener = object : GestureDetector.SimpleOnGestureListener() {
+        override fun onSingleTapUp(e: MotionEvent?): Boolean {
+            onGestureDetected()
+            return true
+        }
+    }
+
+    private var gestureDetector: GestureDetector? = null
+
+    override fun onInputEvent(ev: InputEvent) {
+        if (ev !is MotionEvent) {
+            return
+        }
+        // Pass all events to [gestureDetector], which will then notify [gestureListener] when a tap
+        // is detected.
+        gestureDetector!!.onTouchEvent(ev)
+    }
+
+    /** Start listening for the tap gesture. */
+    override fun startGestureListening() {
+        super.startGestureListening()
+        gestureDetector = GestureDetector(context, gestureListener)
+    }
+
+    /** Stop listening for the swipe gesture. */
+    override fun stopGestureListening() {
+        super.stopGestureListening()
+        gestureDetector = null
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ForegroundServiceDismissalFeatureController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ForegroundServiceDismissalFeatureController.kt
deleted file mode 100644
index 314051c..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ForegroundServiceDismissalFeatureController.kt
+++ /dev/null
@@ -1,49 +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.systemui.statusbar.notification
-
-import android.content.Context
-import android.provider.DeviceConfig
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_ALLOW_FGS_DISMISSAL
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.util.DeviceConfigProxy
-import javax.inject.Inject
-
-private var sIsEnabled: Boolean? = null
-
-/**
- * Feature controller for NOTIFICATIONS_ALLOW_FGS_DISMISSAL config.
- */
-// TODO: this is really boilerplatey, make a base class that just wraps the device config
-@SysUISingleton
-class ForegroundServiceDismissalFeatureController @Inject constructor(
-    val proxy: DeviceConfigProxy,
-    val context: Context
-) {
-    fun isForegroundServiceDismissalEnabled(): Boolean {
-        return isEnabled(proxy)
-    }
-}
-
-private fun isEnabled(proxy: DeviceConfigProxy): Boolean {
-    if (sIsEnabled == null) {
-        sIsEnabled = proxy.getBoolean(
-                DeviceConfig.NAMESPACE_SYSTEMUI, NOTIFICATIONS_ALLOW_FGS_DISMISSAL, false)
-    }
-
-    return sIsEnabled!!
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index f97b936..1d96de7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -111,7 +111,6 @@
     private final Lazy<NotificationRowBinder> mNotificationRowBinderLazy;
     private final Lazy<NotificationRemoteInputManager> mRemoteInputManagerLazy;
     private final LeakDetector mLeakDetector;
-    private final ForegroundServiceDismissalFeatureController mFgsFeatureController;
     private final IStatusBarService mStatusBarService;
     private final NotifLiveDataStoreImpl mNotifLiveDataStore;
     private final DumpManager mDumpManager;
@@ -159,7 +158,6 @@
             Lazy<NotificationRowBinder> notificationRowBinderLazy,
             Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
             LeakDetector leakDetector,
-            ForegroundServiceDismissalFeatureController fgsFeatureController,
             IStatusBarService statusBarService,
             NotifLiveDataStoreImpl notifLiveDataStore,
             DumpManager dumpManager
@@ -170,7 +168,6 @@
         mNotificationRowBinderLazy = notificationRowBinderLazy;
         mRemoteInputManagerLazy = notificationRemoteInputManagerLazy;
         mLeakDetector = leakDetector;
-        mFgsFeatureController = fgsFeatureController;
         mStatusBarService = statusBarService;
         mNotifLiveDataStore = notifLiveDataStore;
         mDumpManager = dumpManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index e3ebef9..53889f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -38,7 +38,6 @@
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.AssistantFeedbackController;
-import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
@@ -128,7 +127,6 @@
             Lazy<NotificationRowBinder> notificationRowBinderLazy,
             Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
             LeakDetector leakDetector,
-            ForegroundServiceDismissalFeatureController fgsFeatureController,
             IStatusBarService statusBarService,
             NotifLiveDataStoreImpl notifLiveDataStore,
             DumpManager dumpManager) {
@@ -139,7 +137,6 @@
                 notificationRowBinderLazy,
                 notificationRemoteInputManagerLazy,
                 leakDetector,
-                fgsFeatureController,
                 statusBarService,
                 notifLiveDataStore,
                 dumpManager);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/DungeonRow.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/DungeonRow.kt
deleted file mode 100644
index dbfa27f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/DungeonRow.kt
+++ /dev/null
@@ -1,43 +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.systemui.statusbar.notification.row
-
-import android.content.Context
-import android.util.AttributeSet
-import android.widget.LinearLayout
-import android.widget.TextView
-import com.android.systemui.R
-import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-
-class DungeonRow(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
-    var entry: NotificationEntry? = null
-        set(value) {
-            field = value
-            update()
-        }
-
-    private fun update() {
-        (findViewById(R.id.app_name) as TextView).apply {
-            text = entry?.row?.appName
-        }
-
-        (findViewById(R.id.icon) as StatusBarIconView).apply {
-            set(entry?.icons?.statusBarIcon?.statusBarIcon)
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ForegroundServiceDungeonView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ForegroundServiceDungeonView.kt
deleted file mode 100644
index 17396ad..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ForegroundServiceDungeonView.kt
+++ /dev/null
@@ -1,38 +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.systemui.statusbar.notification.row
-
-import android.content.Context
-import android.util.AttributeSet
-import android.view.View
-
-import com.android.systemui.R
-
-class ForegroundServiceDungeonView(context: Context, attrs: AttributeSet)
-    : StackScrollerDecorView(context, attrs) {
-    override fun findContentView(): View? {
-        return findViewById(R.id.foreground_service_dungeon)
-    }
-
-    override fun findSecondaryView(): View? {
-        return null
-    }
-
-    override fun setVisible(visible: Boolean, animate: Boolean) {
-        // Visibility is controlled by the ForegroundServiceSectionController
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ForegroundServiceSectionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ForegroundServiceSectionController.kt
deleted file mode 100644
index 75ca337..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ForegroundServiceSectionController.kt
+++ /dev/null
@@ -1,168 +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.systemui.statusbar.notification.stack
-
-import android.content.Context
-import android.service.notification.NotificationListenerService.REASON_CANCEL
-import android.service.notification.NotificationListenerService.REASON_CANCEL_ALL
-import android.service.notification.NotificationListenerService.REASON_CLICK
-import android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED
-import android.view.LayoutInflater
-import android.view.View
-import android.widget.LinearLayout
-import com.android.systemui.R
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController
-import com.android.systemui.statusbar.notification.NotificationEntryListener
-import com.android.systemui.statusbar.notification.NotificationEntryManager
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.row.DungeonRow
-import com.android.systemui.util.Assert
-import javax.inject.Inject
-
-/**
- * Controller for the bottom area of NotificationStackScrollLayout. It owns swiped-away foreground
- * service notifications and can reinstantiate them when requested.
- */
-@SysUISingleton
-class ForegroundServiceSectionController @Inject constructor(
-    val entryManager: NotificationEntryManager,
-    val featureController: ForegroundServiceDismissalFeatureController
-) {
-    private val TAG = "FgsSectionController"
-    private var context: Context? = null
-
-    private val entries = mutableSetOf<NotificationEntry>()
-
-    private var entriesView: View? = null
-
-    init {
-        if (featureController.isForegroundServiceDismissalEnabled()) {
-            entryManager.addNotificationRemoveInterceptor(this::shouldInterceptRemoval)
-
-            entryManager.addNotificationEntryListener(object : NotificationEntryListener {
-                override fun onPostEntryUpdated(entry: NotificationEntry) {
-                    if (entries.contains(entry)) {
-                        removeEntry(entry)
-                        addEntry(entry)
-                        update()
-                    }
-                }
-            })
-        }
-    }
-
-    private fun shouldInterceptRemoval(
-        key: String,
-        entry: NotificationEntry?,
-        reason: Int
-    ): Boolean {
-        Assert.isMainThread()
-        val isClearAll = reason == REASON_CANCEL_ALL
-        val isUserDismiss = reason == REASON_CANCEL || reason == REASON_CLICK
-        // REASON_APP_CANCEL and REASON_APP_CANCEL_ALL are ignored, because the
-        // foreground service associated with it is gone.
-        val isSummaryCancel = reason == REASON_GROUP_SUMMARY_CANCELED
-
-        if (entry == null) return false
-
-        // We only want to retain notifications that the user dismissed
-        // TODO: centralize the entry.isClearable logic and this so that it's clear when a notif is
-        // clearable
-        if (isUserDismiss && !entry.sbn.isClearable) {
-            if (!hasEntry(entry)) {
-                addEntry(entry)
-                update()
-            }
-            // TODO: This isn't ideal. Slightly better would at least be to have NEM update the
-            // notif list when an entry gets intercepted
-            entryManager.updateNotifications(
-                    "FgsSectionController.onNotificationRemoveRequested")
-            return true
-        } else if ((isClearAll || isSummaryCancel) && !entry.sbn.isClearable) {
-            // In the case where a FGS notification is part of a group that is cleared or a clear
-            // all, we actually want to stop its removal but also not put it into the dungeon
-            return true
-        } else if (hasEntry(entry)) {
-            removeEntry(entry)
-            update()
-            return false
-        }
-
-        return false
-    }
-
-    private fun removeEntry(entry: NotificationEntry) {
-        Assert.isMainThread()
-        entries.remove(entry)
-    }
-
-    private fun addEntry(entry: NotificationEntry) {
-        Assert.isMainThread()
-        entries.add(entry)
-    }
-
-    fun hasEntry(entry: NotificationEntry): Boolean {
-        Assert.isMainThread()
-        return entries.contains(entry)
-    }
-
-    fun initialize(context: Context) {
-        this.context = context
-    }
-
-    fun createView(li: LayoutInflater): View {
-        entriesView = li.inflate(R.layout.foreground_service_dungeon, null)
-        // Start out gone
-        entriesView!!.visibility = View.GONE
-        return entriesView!!
-    }
-
-    private fun update() {
-        Assert.isMainThread()
-        if (entriesView == null) {
-            throw IllegalStateException("ForegroundServiceSectionController is trying to show " +
-                    "dismissed fgs notifications without having been initialized!")
-        }
-
-        // TODO: these views should be recycled and not inflating on the main thread
-        (entriesView!!.findViewById(R.id.entry_list) as LinearLayout).apply {
-            removeAllViews()
-            entries.sortedBy { it.ranking.rank }.forEach { entry ->
-                val child = LayoutInflater.from(context)
-                        .inflate(R.layout.foreground_service_dungeon_row, null) as DungeonRow
-
-                child.entry = entry
-                child.setOnClickListener {
-                    removeEntry(child.entry!!)
-                    update()
-                    entry.row.unDismiss()
-                    entry.row.resetTranslation()
-                    entryManager.updateNotifications("ForegroundServiceSectionController.onClick")
-                }
-
-                addView(child)
-            }
-        }
-
-        if (entries.isEmpty()) {
-            entriesView?.visibility = View.GONE
-        } else {
-            entriesView?.visibility = View.VISIBLE
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 2c4db77..f8096437 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -100,7 +100,6 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.FooterView;
-import com.android.systemui.statusbar.notification.row.ForegroundServiceDungeonView;
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
@@ -453,7 +452,6 @@
     private Interpolator mHideXInterpolator = Interpolators.FAST_OUT_SLOW_IN;
 
     private final NotificationSectionsManager mSectionsManager;
-    private ForegroundServiceDungeonView mFgsSectionView;
     private boolean mAnimateBottomOnLayout;
     private float mLastSentAppear;
     private float mLastSentExpandedHeight;
@@ -614,14 +612,6 @@
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
     }
 
-    void initializeForegroundServiceSection(ForegroundServiceDungeonView fgsSectionView) {
-        if (mFgsSectionView != null) {
-            return;
-        }
-        mFgsSectionView = fgsSectionView;
-        addView(mFgsSectionView, -1);
-    }
-
     /**
      * Set the overexpansion of the panel to be applied to the view.
      */
@@ -5286,9 +5276,6 @@
         // incremented in the following "changeViewPosition" calls so that its value is correct for
         // subsequent calls.
         int offsetFromEnd = 1;
-        if (mFgsSectionView != null) {
-            changeViewPosition(mFgsSectionView, getChildCount() - offsetFromEnd++);
-        }
         changeViewPosition(mFooterView, getChildCount() - offsetFromEnd++);
         changeViewPosition(mEmptyShadeView, getChildCount() - offsetFromEnd++);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index d1c63e3..9b4f8b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -86,7 +86,6 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.ExpandAnimationParameters;
-import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -109,7 +108,6 @@
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.notification.row.ForegroundServiceDungeonView;
 import com.android.systemui.statusbar.notification.row.NotificationGuts;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.NotificationSnooze;
@@ -170,8 +168,6 @@
     private final NotificationEntryManager mNotificationEntryManager;
     private final IStatusBarService mIStatusBarService;
     private final UiEventLogger mUiEventLogger;
-    private final ForegroundServiceDismissalFeatureController mFgFeatureController;
-    private final ForegroundServiceSectionController mFgServicesSectionController;
     private final LayoutInflater mLayoutInflater;
     private final NotificationRemoteInputManager mRemoteInputManager;
     private final VisualStabilityManager mVisualStabilityManager;
@@ -660,8 +656,6 @@
             LockscreenShadeTransitionController lockscreenShadeTransitionController,
             IStatusBarService iStatusBarService,
             UiEventLogger uiEventLogger,
-            ForegroundServiceDismissalFeatureController fgFeatureController,
-            ForegroundServiceSectionController fgServicesSectionController,
             LayoutInflater layoutInflater,
             NotificationRemoteInputManager remoteInputManager,
             VisualStabilityManager visualStabilityManager,
@@ -709,8 +703,6 @@
         mNotificationEntryManager = notificationEntryManager;
         mIStatusBarService = iStatusBarService;
         mUiEventLogger = uiEventLogger;
-        mFgFeatureController = fgFeatureController;
-        mFgServicesSectionController = fgServicesSectionController;
         mLayoutInflater = layoutInflater;
         mRemoteInputManager = remoteInputManager;
         mVisualStabilityManager = visualStabilityManager;
@@ -744,12 +736,6 @@
         mNotificationRoundnessManager.setShouldRoundPulsingViews(
                 !mKeyguardBypassController.getBypassEnabled());
 
-        if (mFgFeatureController.isForegroundServiceDismissalEnabled()) {
-            mView.initializeForegroundServiceSection(
-                    (ForegroundServiceDungeonView) mFgServicesSectionController.createView(
-                            mLayoutInflater));
-        }
-
         mSwipeHelper = mNotificationSwipeHelperBuilder
                 .setSwipeDirection(SwipeHelper.X)
                 .setNotificationCallback(mNotificationCallback)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index b141110..8b25c2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -16,9 +16,14 @@
 
 package com.android.systemui.statusbar.phone;
 
+import android.content.ContentResolver;
+import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.database.ContentObserver;
 import android.hardware.display.AmbientDisplayConfiguration;
+import android.net.Uri;
+import android.os.Handler;
 import android.os.PowerManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -34,6 +39,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 import com.android.systemui.doze.DozeScreenState;
@@ -86,6 +92,7 @@
 
     private boolean mDozeAlwaysOn;
     private boolean mControlScreenOffAnimation;
+    private boolean mIsQuickPickupEnabled;
 
     private boolean mKeyguardShowing;
     @VisibleForTesting
@@ -101,10 +108,17 @@
                 public void onShadeExpandedChanged(boolean expanded) {
                     updateControlScreenOff();
                 }
+
+                @Override
+                public void onUserSwitchComplete(int newUserId) {
+                    updateQuickPickupEnabled();
+                }
             };
 
     @Inject
     protected DozeParameters(
+            Context context,
+            @Background Handler handler,
             @Main Resources resources,
             AmbientDisplayConfiguration ambientDisplayConfiguration,
             AlwaysOnDisplayPolicy alwaysOnDisplayPolicy,
@@ -146,6 +160,14 @@
         if (mFoldAodAnimationController != null) {
             mFoldAodAnimationController.addCallback(this);
         }
+
+        SettingsObserver quickPickupSettingsObserver = new SettingsObserver(context, handler);
+        quickPickupSettingsObserver.observe();
+    }
+
+    private void updateQuickPickupEnabled() {
+        mIsQuickPickupEnabled =
+                mAmbientDisplayConfiguration.quickPickupSensorEnabled(UserHandle.USER_CURRENT);
     }
 
     public boolean getDisplayStateSupported() {
@@ -239,8 +261,11 @@
         return mDozeAlwaysOn && !mBatteryController.isAodPowerSave();
     }
 
+    /**
+     * Whether the quick pickup gesture is supported and enabled for the device.
+     */
     public boolean isQuickPickupEnabled() {
-        return mAmbientDisplayConfiguration.quickPickupSensorEnabled(UserHandle.USER_CURRENT);
+        return mIsQuickPickupEnabled;
     }
 
     /**
@@ -436,6 +461,7 @@
         pw.print("getPickupVibrationThreshold(): "); pw.println(getPickupVibrationThreshold());
         pw.print("getSelectivelyRegisterSensorsUsingProx(): ");
         pw.println(getSelectivelyRegisterSensorsUsingProx());
+        pw.print("isQuickPickupEnabled(): "); pw.println(isQuickPickupEnabled());
     }
 
     private boolean getPostureSpecificBool(
@@ -458,4 +484,44 @@
          */
         void onAlwaysOnChange();
     }
+
+    private final class SettingsObserver extends ContentObserver {
+        private final Uri mQuickPickupGesture =
+                Settings.Secure.getUriFor(Settings.Secure.DOZE_QUICK_PICKUP_GESTURE);
+        private final Uri mPickupGesture =
+                Settings.Secure.getUriFor(Settings.Secure.DOZE_PICK_UP_GESTURE);
+        private final Uri mAlwaysOnEnabled =
+                Settings.Secure.getUriFor(Settings.Secure.DOZE_ALWAYS_ON);
+        private final Context mContext;
+
+        SettingsObserver(Context context, Handler handler) {
+            super(handler);
+            mContext = context;
+        }
+
+        void observe() {
+            ContentResolver resolver = mContext.getContentResolver();
+            resolver.registerContentObserver(mQuickPickupGesture, false, this,
+                    UserHandle.USER_ALL);
+            resolver.registerContentObserver(mPickupGesture, false, this, UserHandle.USER_ALL);
+            resolver.registerContentObserver(mAlwaysOnEnabled, false, this, UserHandle.USER_ALL);
+            update(null);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            update(uri);
+        }
+
+        public void update(Uri uri) {
+            if (uri == null
+                    || mQuickPickupGesture.equals(uri)
+                    || mPickupGesture.equals(uri)
+                    || mAlwaysOnEnabled.equals(uri)) {
+                // the quick pickup gesture is dependent on alwaysOn being disabled and
+                // the pickup gesture being enabled
+                updateQuickPickupEnabled();
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index 9f9e7d9..5caf4f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -26,6 +26,7 @@
 import android.app.IActivityManager;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
 import android.graphics.PixelFormat;
 import android.graphics.Region;
 import android.os.Binder;
@@ -117,6 +118,7 @@
      * @see #batchApplyWindowLayoutParams(Runnable)
      */
     private int mDeferWindowLayoutParams;
+    private boolean mLastKeyguardRotationAllowed;
 
     @Inject
     public NotificationShadeWindowControllerImpl(Context context, WindowManager windowManager,
@@ -143,7 +145,7 @@
         mScreenOffAnimationController = screenOffAnimationController;
         dumpManager.registerDumpable(getClass().getName(), this);
         mAuthController = authController;
-
+        mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed();
         mLockScreenDisplayTimeout = context.getResources()
                 .getInteger(R.integer.config_lockScreenDisplayTimeout);
         ((SysuiStatusBarStateController) statusBarStateController)
@@ -779,6 +781,17 @@
         setKeyguardDark(useDarkText);
     }
 
+    @Override
+    public void onConfigChanged(Configuration newConfig) {
+        final boolean newScreenRotationAllowed = mKeyguardStateController
+                .isKeyguardScreenRotationAllowed();
+
+        if (mLastKeyguardRotationAllowed != newScreenRotationAllowed) {
+            apply(mCurrentState);
+            mLastKeyguardRotationAllowed = newScreenRotationAllowed;
+        }
+    }
+
     /**
      * When keyguard will be dismissed but didn't start animation yet.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
index 72237b1..53ef97d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
@@ -27,7 +27,6 @@
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.wm.shell.bubbles.Bubbles;
 
 import java.util.ArrayList;
 import java.util.Optional;
@@ -50,7 +49,6 @@
     private final int mDisplayId;
     protected final Lazy<Optional<StatusBar>> mStatusBarOptionalLazy;
     private final Lazy<AssistManager> mAssistManagerLazy;
-    private final Optional<Bubbles> mBubblesOptional;
 
     private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
 
@@ -62,8 +60,7 @@
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             WindowManager windowManager,
             Lazy<Optional<StatusBar>> statusBarOptionalLazy,
-            Lazy<AssistManager> assistManagerLazy,
-            Optional<Bubbles> bubblesOptional
+            Lazy<AssistManager> assistManagerLazy
     ) {
         mCommandQueue = commandQueue;
         mStatusBarStateController = statusBarStateController;
@@ -73,7 +70,6 @@
         // TODO: Remove circular reference to StatusBar when possible.
         mStatusBarOptionalLazy = statusBarOptionalLazy;
         mAssistManagerLazy = assistManagerLazy;
-        mBubblesOptional = bubblesOptional;
     }
 
     @Override
@@ -131,8 +127,6 @@
             getStatusBar().getNotificationShadeWindowViewController().cancelExpandHelper();
             getNotificationPanelViewController()
                     .collapsePanel(true /* animate */, delayed, speedUpFactor);
-        } else if (mBubblesOptional.isPresent()) {
-            mBubblesOptional.get().collapseStack();
         }
     }
 
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 82e0e67..cffdc29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -270,6 +270,7 @@
     protected static final int MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU = 1027;
 
     // Should match the values in PhoneWindowManager
+    public static final String SYSTEM_DIALOG_REASON_KEY = "reason";
     public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
     public static final String SYSTEM_DIALOG_REASON_DREAM = "dream";
     static public final String SYSTEM_DIALOG_REASON_SCREENSHOT = "screenshot";
@@ -2661,15 +2662,12 @@
             Trace.beginSection("StatusBar#onReceive");
             if (DEBUG) Log.v(TAG, "onReceive: " + intent);
             String action = intent.getAction();
+            String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
             if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
                 KeyboardShortcuts.dismiss();
                 mRemoteInputManager.closeRemoteInputs();
-                if (mBubblesOptional.isPresent() && mBubblesOptional.get().isStackExpanded()) {
-                    mBubblesOptional.get().collapseStack();
-                }
                 if (mLockscreenUserManager.isCurrentProfile(getSendingUserId())) {
                     int flags = CommandQueue.FLAG_EXCLUDE_NONE;
-                    String reason = intent.getStringExtra("reason");
                     if (reason != null) {
                         if (reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {
                             flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL;
@@ -2684,19 +2682,13 @@
                     }
                     mShadeController.animateCollapsePanels(flags);
                 }
-            }
-            else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+            } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
                 if (mNotificationShadeWindowController != null) {
                     mNotificationShadeWindowController.setNotTouchable(false);
                 }
-                if (mBubblesOptional.isPresent() && mBubblesOptional.get().isStackExpanded()) {
-                    // Post to main thread, since updating the UI.
-                    mMainExecutor.execute(() -> mBubblesOptional.get().collapseStack());
-                }
                 finishBarAnimations();
                 resetUserExpandedStates();
-            }
-            else if (DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG.equals(action)) {
+            } else if (DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG.equals(action)) {
                 mQSPanelController.showDeviceMonitoringDialog();
             }
             Trace.endSection();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 11d9c31..a96ba56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -1077,7 +1077,8 @@
                 && mBiometricUnlockController.getMode() == MODE_WAKE_AND_UNLOCK_PULSING;
         boolean keyguardShowing = mShowing && !mOccluded;
         boolean hideWhileDozing = mDozing && !isWakeAndUnlockPulsing;
-        boolean keyguardWithGestureNav = (keyguardShowing && !mDozing || mPulsing && !mIsDocked)
+        boolean keyguardWithGestureNav = (keyguardShowing && !mDozing && !mScreenOffAnimationPlaying
+                || mPulsing && !mIsDocked)
                 && mGesturalNav;
         return (!keyguardShowing && !hideWhileDozing && !mScreenOffAnimationPlaying
                 || mBouncer.isShowing() || mRemoteInputActive || keyguardWithGestureNav
@@ -1091,7 +1092,8 @@
         boolean keyguardShowing = mLastShowing && !mLastOccluded;
         boolean hideWhileDozing = mLastDozing && mLastBiometricMode != MODE_WAKE_AND_UNLOCK_PULSING;
         boolean keyguardWithGestureNav = (keyguardShowing && !mLastDozing
-                || mLastPulsing && !mLastIsDocked) && mLastGesturalNav;
+                && !mLastScreenOffAnimationPlaying || mLastPulsing && !mLastIsDocked)
+                && mLastGesturalNav;
         return (!keyguardShowing && !hideWhileDozing && !mLastScreenOffAnimationPlaying
                 || mLastBouncerShowing || mLastRemoteInputActive || keyguardWithGestureNav
                 || mLastGlobalActionsVisible);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 9722528..79d646c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -42,7 +42,10 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.shared.system.QuickStepContract;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -64,7 +67,8 @@
     private final Context mContext;
     @Nullable private final DismissReceiver mDismissReceiver;
     private final Handler mHandler = new Handler();
-    @Nullable private final SystemUIDialogManager mDialogManager;
+    private final SystemUIDialogManager mDialogManager;
+    private final SysUiState mSysUiState;
 
     private int mLastWidth = Integer.MIN_VALUE;
     private int mLastHeight = Integer.MIN_VALUE;
@@ -77,24 +81,11 @@
         this(context, R.style.Theme_SystemUI_Dialog);
     }
 
-    public SystemUIDialog(Context context, SystemUIDialogManager dialogManager) {
-        this(context, R.style.Theme_SystemUI_Dialog, true, dialogManager);
-    }
-
     public SystemUIDialog(Context context, int theme) {
         this(context, theme, true /* dismissOnDeviceLock */);
     }
 
-    public SystemUIDialog(Context context, int theme, SystemUIDialogManager dialogManager) {
-        this(context, theme, true /* dismissOnDeviceLock */, dialogManager);
-    }
-
     public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock) {
-        this(context, theme, dismissOnDeviceLock, null);
-    }
-
-    public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock,
-            @Nullable SystemUIDialogManager dialogManager) {
         super(context, theme);
         mContext = context;
 
@@ -104,7 +95,12 @@
         getWindow().setAttributes(attrs);
 
         mDismissReceiver = dismissOnDeviceLock ? new DismissReceiver(this) : null;
-        mDialogManager = dialogManager;
+
+        // TODO(b/219008720): Remove those calls to Dependency.get by introducing a
+        // SystemUIDialogFactory and make all other dialogs create a SystemUIDialog to which we set
+        // the content and attach listeners.
+        mDialogManager = Dependency.get(SystemUIDialogManager.class);
+        mSysUiState = Dependency.get(SysUiState.class);
     }
 
     @Override
@@ -174,13 +170,11 @@
             mDismissReceiver.register();
         }
 
-        if (mDialogManager != null) {
-            mDialogManager.setShowing(this, true);
-        }
-
         // Listen for configuration changes to resize this dialog window. This is mostly necessary
         // for foldables that often go from large <=> small screen when folding/unfolding.
         ViewRootImpl.addConfigCallback(this);
+        mDialogManager.setShowing(this, true);
+        mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, true);
     }
 
     @Override
@@ -191,11 +185,9 @@
             mDismissReceiver.unregister();
         }
 
-        if (mDialogManager != null) {
-            mDialogManager.setShowing(this, false);
-        }
-
         ViewRootImpl.removeConfigCallback(this);
+        mDialogManager.setShowing(this, false);
+        mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, false);
     }
 
     public void setShowForAllUsers(boolean show) {
@@ -401,10 +393,13 @@
         private final Dialog mDialog;
         private boolean mRegistered;
         private final BroadcastDispatcher mBroadcastDispatcher;
+        private final DialogLaunchAnimator mDialogLaunchAnimator;
 
         DismissReceiver(Dialog dialog) {
             mDialog = dialog;
+            // TODO(b/219008720): Remove those calls to Dependency.get.
             mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
+            mDialogLaunchAnimator = Dependency.get(DialogLaunchAnimator.class);
         }
 
         void register() {
@@ -421,6 +416,10 @@
 
         @Override
         public void onReceive(Context context, Intent intent) {
+            // These broadcast are usually received when locking the device, swiping up to home
+            // (which collapses the shade), etc. In those cases, we usually don't want to animate
+            // back into the view.
+            mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
             mDialog.dismiss();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index c7f7258..0abcaaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -21,6 +21,7 @@
 import android.app.IUidObserver
 import android.app.Notification
 import android.app.Notification.CallStyle.CALL_TYPE_ONGOING
+import android.content.Context
 import android.content.Intent
 import android.util.Log
 import android.view.View
@@ -52,6 +53,7 @@
  */
 @SysUISingleton
 class OngoingCallController @Inject constructor(
+    private val context: Context,
     private val notifCollection: CommonNotifCollection,
     private val ongoingCallFlags: OngoingCallFlags,
     private val systemClock: SystemClock,
@@ -67,13 +69,10 @@
     private var isFullscreen: Boolean = false
     /** Non-null if there's an active call notification. */
     private var callNotificationInfo: CallNotificationInfo? = null
-    /** True if the application managing the call is visible to the user. */
-    private var isCallAppVisible: Boolean = false
     private var chipView: View? = null
-    private var uidObserver: IUidObserver.Stub? = null
 
     private val mListeners: MutableList<OngoingCallListener> = mutableListOf()
-
+    private val uidObserver = CallAppUidObserver()
     private val notifListener = object : NotifCollectionListener {
         // Temporary workaround for b/178406514 for testing purposes.
         //
@@ -158,7 +157,7 @@
     fun hasOngoingCall(): Boolean {
         return callNotificationInfo?.isOngoing == true &&
                 // When the user is in the phone app, don't show the chip.
-                !isCallAppVisible
+                !uidObserver.isCallAppVisible
     }
 
     override fun addCallback(listener: OngoingCallListener) {
@@ -194,7 +193,7 @@
             }
             updateChipClickListener()
 
-            setUpUidObserver(currentCallNotificationInfo)
+            uidObserver.registerWithUid(currentCallNotificationInfo.uid)
             if (!currentCallNotificationInfo.statusBarSwipedAway) {
                 statusBarWindowController.ifPresent {
                     it.setOngoingProcessRequiresStatusBarVisible(true)
@@ -238,51 +237,6 @@
         }
     }
 
-    /**
-     * Sets up an [IUidObserver] to monitor the status of the application managing the ongoing call.
-     */
-    private fun setUpUidObserver(currentCallNotificationInfo: CallNotificationInfo) {
-        isCallAppVisible = isProcessVisibleToUser(
-                iActivityManager.getUidProcessState(currentCallNotificationInfo.uid, null))
-
-        if (uidObserver != null) {
-            iActivityManager.unregisterUidObserver(uidObserver)
-        }
-
-        uidObserver = object : IUidObserver.Stub() {
-            override fun onUidStateChanged(
-                uid: Int,
-                procState: Int,
-                procStateSeq: Long,
-                capability: Int
-            ) {
-                if (uid == currentCallNotificationInfo.uid) {
-                    val oldIsCallAppVisible = isCallAppVisible
-                    isCallAppVisible = isProcessVisibleToUser(procState)
-                    if (oldIsCallAppVisible != isCallAppVisible) {
-                        // Animations may be run as a result of the call's state change, so ensure
-                        // the listener is notified on the main thread.
-                        mainExecutor.execute {
-                            mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
-                        }
-                    }
-                }
-            }
-
-            override fun onUidGone(uid: Int, disabled: Boolean) {}
-            override fun onUidActive(uid: Int) {}
-            override fun onUidIdle(uid: Int, disabled: Boolean) {}
-            override fun onUidCachedChanged(uid: Int, cached: Boolean) {}
-        }
-
-        iActivityManager.registerUidObserver(
-                uidObserver,
-                ActivityManager.UID_OBSERVER_PROCSTATE,
-                ActivityManager.PROCESS_STATE_UNKNOWN,
-                null
-        )
-    }
-
     /** Returns true if the given [procState] represents a process that's visible to the user. */
     private fun isProcessVisibleToUser(procState: Int): Boolean {
         return procState <= ActivityManager.PROCESS_STATE_TOP
@@ -306,9 +260,7 @@
         statusBarWindowController.ifPresent { it.setOngoingProcessRequiresStatusBarVisible(false) }
         swipeStatusBarAwayGestureHandler.ifPresent { it.removeOnGestureDetectedCallback(TAG) }
         mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
-        if (uidObserver != null) {
-            iActivityManager.unregisterUidObserver(uidObserver)
-        }
+        uidObserver.unregister()
     }
 
     /** Tear down anything related to the chip view to prevent leaks. */
@@ -365,7 +317,84 @@
 
     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
         pw.println("Active call notification: $callNotificationInfo")
-        pw.println("Call app visible: $isCallAppVisible")
+        pw.println("Call app visible: ${uidObserver.isCallAppVisible}")
+    }
+
+    /** Our implementation of a [IUidObserver]. */
+    inner class CallAppUidObserver : IUidObserver.Stub() {
+        /** True if the application managing the call is visible to the user. */
+        var isCallAppVisible: Boolean = false
+            private set
+
+        /** The UID of the application managing the call. Null if there is no active call. */
+        private var callAppUid: Int? = null
+
+        /**
+         * True if this observer is currently registered with the activity manager and false
+         * otherwise.
+         */
+        private var isRegistered = false
+
+
+        /** Register this observer with the activity manager and the given [uid]. */
+        fun registerWithUid(uid: Int) {
+            if (callAppUid == uid) {
+                return
+            }
+            callAppUid = uid
+
+            try {
+                isCallAppVisible = isProcessVisibleToUser(
+                    iActivityManager.getUidProcessState(uid, context.opPackageName)
+                )
+                if (isRegistered) {
+                    return
+                }
+                iActivityManager.registerUidObserver(
+                    uidObserver,
+                    ActivityManager.UID_OBSERVER_PROCSTATE,
+                    ActivityManager.PROCESS_STATE_UNKNOWN,
+                    context.opPackageName
+                )
+                isRegistered = true
+            } catch (se: SecurityException) {
+                Log.e(TAG, "Security exception when trying to set up uid observer: $se")
+            }
+        }
+
+        /** Unregister this observer with the activity manager. */
+        fun unregister() {
+            callAppUid = null
+            isRegistered = false
+            iActivityManager.unregisterUidObserver(uidObserver)
+        }
+
+        override fun onUidStateChanged(
+            uid: Int,
+            procState: Int,
+            procStateSeq: Long,
+            capability: Int
+        ) {
+            val currentCallAppUid = callAppUid ?: return
+            if (uid != currentCallAppUid) {
+                return
+            }
+
+            val oldIsCallAppVisible = isCallAppVisible
+            isCallAppVisible = isProcessVisibleToUser(procState)
+            if (oldIsCallAppVisible != isCallAppVisible) {
+                // Animations may be run as a result of the call's state change, so ensure
+                // the listener is notified on the main thread.
+                mainExecutor.execute {
+                    mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
+                }
+            }
+        }
+
+        override fun onUidGone(uid: Int, disabled: Boolean) {}
+        override fun onUidActive(uid: Int) {}
+        override fun onUidIdle(uid: Int, disabled: Boolean) {}
+        override fun onUidCachedChanged(uid: Int, cached: Boolean) {}
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index 6f587fd..c53d510 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -68,6 +68,7 @@
     private final StatusBarContentInsetsProvider mContentInsetsProvider;
     private int mBarHeight = -1;
     private final State mCurrentState = new State();
+    private boolean mIsAttached;
 
     private final ViewGroup mStatusBarWindowView;
     // The container in which we should run launch animations started from the status bar and
@@ -136,6 +137,8 @@
 
         mContentInsetsProvider.addCallback(this::calculateStatusBarLocationsForAllRotations);
         calculateStatusBarLocationsForAllRotations();
+        mIsAttached = true;
+        apply(mCurrentState);
     }
 
     /** Adds the given view to the status bar window view. */
@@ -282,6 +285,9 @@
     }
 
     private void apply(State state) {
+        if (!mIsAttached) {
+            return;
+        }
         applyForceStatusBarVisibleFlag(state);
         applyHeight(state);
         if (mLp != null && mLp.copyFrom(mLpChanged) != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java b/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java
new file mode 100644
index 0000000..de4e1e2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.touch;
+
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.View;
+import android.view.ViewRootImpl;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.concurrent.Executor;
+
+/**
+ * {@link TouchInsetManager} handles setting the touchable inset regions for a given View. This
+ * is useful for passing through touch events for all but select areas.
+ */
+public class TouchInsetManager {
+    /**
+     * {@link TouchInsetSession} provides an individualized session with the
+     * {@link TouchInsetManager}, linking any action to the client.
+     */
+    public static class TouchInsetSession {
+        private final TouchInsetManager mManager;
+
+        private final HashSet<View> mTrackedViews;
+        private final Executor mExecutor;
+
+        private final View.OnLayoutChangeListener mOnLayoutChangeListener =
+                (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom)
+                        -> updateTouchRegion();
+
+        /**
+         * Default constructor
+         * @param manager The parent {@link TouchInsetManager} which will be affected by actions on
+         *                this session.
+         * @param rootView The parent of views that will be tracked.
+         * @param executor An executor for marshalling operations.
+         */
+        TouchInsetSession(TouchInsetManager manager, Executor executor) {
+            mManager = manager;
+            mTrackedViews = new HashSet<>();
+            mExecutor = executor;
+        }
+
+        /**
+         * Adds a descendant of the root view to be tracked.
+         * @param view {@link View} to be tracked.
+         */
+        public void addViewToTracking(View view) {
+            mExecutor.execute(() -> {
+                mTrackedViews.add(view);
+                view.addOnLayoutChangeListener(mOnLayoutChangeListener);
+                updateTouchRegion();
+            });
+        }
+
+        /**
+         * Removes a view from further tracking
+         * @param view {@link View} to be removed.
+         */
+        public void removeViewFromTracking(View view) {
+            mExecutor.execute(() -> {
+                mTrackedViews.remove(view);
+                view.removeOnLayoutChangeListener(mOnLayoutChangeListener);
+                updateTouchRegion();
+            });
+        }
+
+        private void updateTouchRegion() {
+            final Region cumulativeRegion = Region.obtain();
+
+            mTrackedViews.stream().forEach(view -> {
+                final Rect boundaries = new Rect();
+                view.getBoundsOnScreen(boundaries);
+                cumulativeRegion.op(boundaries, Region.Op.UNION);
+            });
+
+            mManager.setTouchRegion(this, cumulativeRegion);
+
+            cumulativeRegion.recycle();
+        }
+
+        /**
+         * Removes all tracked views and updates insets accordingly.
+         */
+        public void clear() {
+            mExecutor.execute(() -> {
+                mManager.clearRegion(this);
+                mTrackedViews.clear();
+            });
+        }
+    }
+
+    private final HashMap<TouchInsetSession, Region> mDefinedRegions = new HashMap<>();
+    private final Executor mExecutor;
+    private final View mRootView;
+
+    private final View.OnAttachStateChangeListener mAttachListener =
+            new View.OnAttachStateChangeListener() {
+                @Override
+                public void onViewAttachedToWindow(View v) {
+                    updateTouchInset();
+                }
+
+                @Override
+                public void onViewDetachedFromWindow(View v) {
+                }
+            };
+
+    /**
+     * Default constructor.
+     * @param executor An {@link Executor} to marshal all operations on.
+     * @param rootView The root {@link View} for all views in sessions.
+     */
+    public TouchInsetManager(Executor executor, View rootView) {
+        mExecutor = executor;
+        mRootView = rootView;
+        mRootView.addOnAttachStateChangeListener(mAttachListener);
+
+    }
+
+    /**
+     * Creates a new associated session.
+     */
+    public TouchInsetSession createSession() {
+        return new TouchInsetSession(this, mExecutor);
+    }
+
+    private void updateTouchInset() {
+        final ViewRootImpl viewRootImpl = mRootView.getViewRootImpl();
+
+        if (viewRootImpl == null) {
+            return;
+        }
+
+        final Region aggregateRegion = Region.obtain();
+
+        for (Region region : mDefinedRegions.values()) {
+            aggregateRegion.op(region, Region.Op.UNION);
+        }
+
+        viewRootImpl.setTouchableRegion(aggregateRegion);
+
+        aggregateRegion.recycle();
+    }
+
+    protected void setTouchRegion(TouchInsetSession session, Region region) {
+        final Region introducedRegion = Region.obtain(region);
+        mExecutor.execute(() -> {
+            mDefinedRegions.put(session, introducedRegion);
+            updateTouchInset();
+        });
+    }
+
+    private void clearRegion(TouchInsetSession session) {
+        mExecutor.execute(() -> {
+            final Region storedRegion = mDefinedRegions.remove(session);
+
+            if (storedRegion != null) {
+                storedRegion.recycle();
+            }
+
+            updateTouchInset();
+        });
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
index 14585fb..c0d7925 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
@@ -35,9 +35,8 @@
 import android.widget.ArrayAdapter
 import android.widget.ImageView
 import android.widget.TextView
-
 import androidx.constraintlayout.helper.widget.Flow
-
+import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.util.UserIcons
 import com.android.settingslib.Utils
 import com.android.systemui.R
@@ -47,12 +46,12 @@
 import com.android.systemui.statusbar.phone.ShadeController
 import com.android.systemui.statusbar.policy.UserSwitcherController
 import com.android.systemui.statusbar.policy.UserSwitcherController.BaseUserAdapter
-import com.android.systemui.statusbar.policy.UserSwitcherController.UserRecord
 import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA
 import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA
+import com.android.systemui.statusbar.policy.UserSwitcherController.UserRecord
 import com.android.systemui.util.LifecycleActivity
-
 import javax.inject.Inject
+import kotlin.math.ceil
 
 private const val USER_VIEW = "user_view"
 
@@ -137,6 +136,18 @@
             return UserIcons.getDefaultUserIcon(resources, item.info.id, false)
         }
 
+        fun getTotalUserViews(): Int {
+            return users.count { item ->
+                !doNotRenderUserView(item)
+            }
+        }
+
+        fun doNotRenderUserView(item: UserRecord): Boolean {
+            return item.isAddUser ||
+                    item.isAddSupervisedUser ||
+                    item.isGuest && item.info == null
+        }
+
         private fun getDrawable(item: UserRecord): Drawable {
             var drawable = if (item.isCurrent && item.isGuest) {
                 getDrawable(R.drawable.ic_avatar_guest_user)
@@ -211,7 +222,8 @@
 
         userSwitcherController.init(parent)
         initBroadcastReceiver()
-        buildUserViews()
+
+        parent.post { buildUserViews() }
     }
 
     private fun showPopupMenu() {
@@ -272,16 +284,32 @@
         }
         parent.removeViews(start, count)
         addUserRecords.clear()
-
         val flow = requireViewById<Flow>(R.id.flow)
+        val totalWidth = parent.width
+        val userViewCount = adapter.getTotalUserViews()
+        val maxColumns = getMaxColumns(userViewCount)
+        val horizontalGap = resources
+            .getDimensionPixelSize(R.dimen.user_switcher_fullscreen_horizontal_gap)
+        val totalWidthOfHorizontalGap = (maxColumns - 1) * horizontalGap
+        val maxWidgetDiameter = (totalWidth - totalWidthOfHorizontalGap) / maxColumns
+
+        flow.setMaxElementsWrap(maxColumns)
+
         for (i in 0 until adapter.getCount()) {
             val item = adapter.getItem(i)
-            if (item.isAddUser ||
-                item.isAddSupervisedUser ||
-                item.isGuest && item.info == null) {
+            if (adapter.doNotRenderUserView(item)) {
                 addUserRecords.add(item)
             } else {
                 val userView = adapter.getView(i, null, parent)
+                userView.requireViewById<ImageView>(R.id.user_switcher_icon).apply {
+                    val lp = layoutParams
+                    if (maxWidgetDiameter < lp.width) {
+                        lp.width = maxWidgetDiameter
+                        lp.height = maxWidgetDiameter
+                        layoutParams = lp
+                    }
+                }
+
                 userView.setId(View.generateViewId())
                 parent.addView(userView)
 
@@ -333,6 +361,11 @@
         broadcastDispatcher.registerReceiver(broadcastReceiver, filter)
     }
 
+    @VisibleForTesting
+    fun getMaxColumns(userCount: Int): Int {
+        return if (userCount < 5) 4 else ceil(userCount / 2.0).toInt()
+    }
+
     private class ItemAdapter(
         val parentContext: Context,
         val resource: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CaptionsToggleImageButton.java b/packages/SystemUI/src/com/android/systemui/volume/CaptionsToggleImageButton.java
index 1862ed3..ae23ca6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/CaptionsToggleImageButton.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/CaptionsToggleImageButton.java
@@ -28,14 +28,11 @@
 import com.android.keyguard.AlphaOptimizedImageButton;
 import com.android.systemui.R;
 
-/** Toggle button in Volume Dialog that allows extra state for when streams are opted-out */
+/** Toggle button in Volume Dialog for controlling system captions state */
 public class CaptionsToggleImageButton extends AlphaOptimizedImageButton {
 
-    private static final int[] OPTED_OUT_STATE = new int[] { R.attr.optedOut };
-
     private ConfirmedTapListener mConfirmedTapListener;
     private boolean mCaptionsEnabled = false;
-    private boolean mOptedOut = false;
 
     private GestureDetector mGestureDetector;
     private GestureDetector.SimpleOnGestureListener mGestureListener =
@@ -60,11 +57,7 @@
 
     @Override
     public int[] onCreateDrawableState(int extraSpace) {
-        int[] state = super.onCreateDrawableState(extraSpace + 1);
-        if (mOptedOut) {
-            mergeDrawableStates(state, OPTED_OUT_STATE);
-        }
-        return state;
+        return super.onCreateDrawableState(extraSpace + 1);
     }
 
     Runnable setCaptionsEnabled(boolean areCaptionsEnabled) {
@@ -95,16 +88,6 @@
         return this.mCaptionsEnabled;
     }
 
-    /** Sets whether or not the current stream has opted out of captions */
-    void setOptedOut(boolean isOptedOut) {
-        this.mOptedOut = isOptedOut;
-        refreshDrawableState();
-    }
-
-    boolean getOptedOut() {
-        return this.mOptedOut;
-    }
-
     void setOnConfirmedTapListener(ConfirmedTapListener listener, Handler handler) {
         mConfirmedTapListener = listener;
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 57c7f11..97e03a6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -54,6 +54,7 @@
 import android.util.Log;
 import android.util.Slog;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.CaptioningManager;
 
 import androidx.lifecycle.Observer;
 
@@ -130,6 +131,7 @@
     private final Receiver mReceiver = new Receiver();
     private final RingerModeObservers mRingerModeObservers;
     private final MediaSessions mMediaSessions;
+    private final CaptioningManager mCaptioningManager;
     protected C mCallbacks = new C();
     private final State mState = new State();
     protected final MediaSessionsCallbacks mMediaSessionsCallbacksW;
@@ -175,7 +177,8 @@
             IAudioService iAudioService,
             AccessibilityManager accessibilityManager,
             PackageManager packageManager,
-            WakefulnessLifecycle wakefulnessLifecycle) {
+            WakefulnessLifecycle wakefulnessLifecycle,
+            CaptioningManager captioningManager) {
         mContext = context.getApplicationContext();
         mPackageManager = packageManager;
         mWakefulnessLifecycle = wakefulnessLifecycle;
@@ -200,6 +203,7 @@
         mVibrator = vibrator;
         mHasVibrator = mVibrator.hasVibrator();
         mAudioService = iAudioService;
+        mCaptioningManager = captioningManager;
 
         boolean accessibilityVolumeStreamActive = accessibilityManager
                 .isAccessibilityVolumeStreamActive();
@@ -307,20 +311,11 @@
     }
 
     public boolean areCaptionsEnabled() {
-        int currentValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                Settings.Secure.ODI_CAPTIONS_ENABLED, 0, UserHandle.USER_CURRENT);
-        return currentValue == 1;
+        return mCaptioningManager.isSystemAudioCaptioningEnabled();
     }
 
     public void setCaptionsEnabled(boolean isEnabled) {
-        Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                Settings.Secure.ODI_CAPTIONS_ENABLED, isEnabled ? 1 : 0, UserHandle.USER_CURRENT);
-    }
-
-    @Override
-    public boolean isCaptionStreamOptedOut() {
-        // TODO(b/129768185): Removing secure setting, to be replaced by sound event listener
-        return false;
+        mCaptioningManager.setSystemAudioCaptioningEnabled(isEnabled);
     }
 
     public void getCaptionsComponentState(boolean fromTooltip) {
@@ -423,6 +418,13 @@
     }
 
     private void onGetCaptionsComponentStateW(boolean fromTooltip) {
+        if (mCaptioningManager.isSystemAudioCaptioningUiEnabled()) {
+            mCallbacks.onCaptionComponentStateChanged(true, fromTooltip);
+            return;
+        }
+
+        // TODO(b/220968335): Remove this check once system captions component migrates
+        // to new CaptioningManager APIs.
         try {
             String componentNameString = mContext.getString(
                     com.android.internal.R.string.config_defaultSystemCaptionsService);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 58f74a0..bfdcbd6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1180,11 +1180,6 @@
         if (mODICaptionsIcon.getCaptionsEnabled() != captionsEnabled) {
             mHandler.post(mODICaptionsIcon.setCaptionsEnabled(captionsEnabled));
         }
-
-        boolean isOptedOut = mController.isCaptionStreamOptedOut();
-        if (mODICaptionsIcon.getOptedOut() != isOptedOut) {
-            mHandler.post(() -> mODICaptionsIcon.setOptedOut(isOptedOut));
-        }
     }
 
     private void onCaptionIconClicked() {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 6fefce2..b2a79b0 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -21,7 +21,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
@@ -97,7 +97,7 @@
         implements CommandQueue.Callbacks, ProtoTraceable<SystemUiTraceProto> {
     private static final String TAG = WMShell.class.getName();
     private static final int INVALID_SYSUI_STATE_MASK =
-            SYSUI_STATE_GLOBAL_ACTIONS_SHOWING
+            SYSUI_STATE_DIALOG_SHOWING
                     | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
                     | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
                     | SYSUI_STATE_BOUNCER_SHOWING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt
new file mode 100644
index 0000000..95aa08d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import android.graphics.Insets
+import android.graphics.PixelFormat
+import android.graphics.Rect
+import android.graphics.RectF
+import android.hardware.graphics.common.DisplayDecorationSupport
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.DisplayCutout
+import android.view.DisplayInfo
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.util.mockito.eq
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ScreenDecorHwcLayerTest : SysuiTestCase() {
+
+    @Mock private lateinit var mockDisplay: Display
+    @Mock private lateinit var mockRootView: View
+
+    private val displayWidth = 100
+    private val displayHeight = 200
+    private val cutoutSize = 10
+    private val roundedSizeTop = 15
+    private val roundedSizeBottom = 20
+
+    private lateinit var decorHwcLayer: ScreenDecorHwcLayer
+    private val cutoutTop: DisplayCutout = DisplayCutout.Builder()
+        .setSafeInsets(Insets.of(0, cutoutSize, 0, 0))
+        .setBoundingRectTop(Rect(1, 0, 2, cutoutSize))
+        .build()
+
+    private val cutoutRight: DisplayCutout = DisplayCutout.Builder()
+        .setSafeInsets(Insets.of(0, 0, cutoutSize, 0))
+        .setBoundingRectRight(Rect(displayWidth - cutoutSize, 50, displayWidth, 52))
+        .build()
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        mContext.orCreateTestableResources.addOverride(
+            R.array.config_displayUniqueIdArray, arrayOf<String>())
+        mContext.orCreateTestableResources.addOverride(
+            R.bool.config_fillMainBuiltInDisplayCutout, true)
+
+        val decorationSupport = DisplayDecorationSupport()
+        decorationSupport.format = PixelFormat.R_8
+        decorHwcLayer = Mockito.spy(ScreenDecorHwcLayer(mContext, decorationSupport))
+        whenever(decorHwcLayer.width).thenReturn(displayWidth)
+        whenever(decorHwcLayer.height).thenReturn(displayHeight)
+        whenever(decorHwcLayer.display).thenReturn(mockDisplay)
+        whenever(decorHwcLayer.rootView).thenReturn(mockRootView)
+        whenever(mockRootView.left).thenReturn(0)
+        whenever(mockRootView.top).thenReturn(0)
+        whenever(mockRootView.right).thenReturn(displayWidth)
+        whenever(mockRootView.bottom).thenReturn(displayHeight)
+    }
+
+    @Test
+    fun testTransparentRegion_noCutout_noRoundedCorner_noProtection() {
+        setupConfigs(null, 0, 0, RectF(), 0f)
+
+        decorHwcLayer.calculateTransparentRect()
+
+        assertThat(decorHwcLayer.transparentRect)
+            .isEqualTo(Rect(0, 0, decorHwcLayer.width, decorHwcLayer.height))
+    }
+
+    @Test
+    fun testTransparentRegion_onlyShortEdgeCutout() {
+        setupConfigs(cutoutTop, 0, 0, RectF(), 0f)
+
+        decorHwcLayer.calculateTransparentRect()
+
+        assertThat(decorHwcLayer.transparentRect)
+            .isEqualTo(Rect(0, cutoutSize, decorHwcLayer.width, decorHwcLayer.height))
+    }
+
+    @Test
+    fun testTransparentRegion_onlyLongEdgeCutout() {
+        setupConfigs(cutoutRight, 0, 0, RectF(), 0f)
+
+        decorHwcLayer.calculateTransparentRect()
+
+        assertThat(decorHwcLayer.transparentRect)
+            .isEqualTo(Rect(0, 0, decorHwcLayer.width - cutoutSize, decorHwcLayer.height))
+    }
+
+    @Test
+    fun testTransparentRegion_onlyRoundedCorners() {
+        setupConfigs(null, roundedSizeTop, roundedSizeBottom, RectF(), 0f)
+
+        decorHwcLayer.calculateTransparentRect()
+
+        assertThat(decorHwcLayer.transparentRect)
+            .isEqualTo(Rect(0, roundedSizeTop, decorHwcLayer.width,
+                decorHwcLayer.height - roundedSizeBottom))
+    }
+
+    @Test
+    fun testTransparentRegion_onlyCutoutProtection() {
+        setupConfigs(null, 0, 0, RectF(48f, 1f, 52f, 5f), 0.5f)
+
+        decorHwcLayer.calculateTransparentRect()
+
+        assertThat(decorHwcLayer.transparentRect)
+            .isEqualTo(Rect(0, 4, decorHwcLayer.width, decorHwcLayer.height))
+
+        decorHwcLayer.cameraProtectionProgress = 1f
+
+        decorHwcLayer.calculateTransparentRect()
+
+        assertThat(decorHwcLayer.transparentRect)
+            .isEqualTo(Rect(0, 5, decorHwcLayer.width, decorHwcLayer.height))
+    }
+
+    @Test
+    fun testTransparentRegion_hasShortEdgeCutout_hasRoundedCorner_hasCutoutProtection() {
+        setupConfigs(cutoutTop, roundedSizeTop, roundedSizeBottom, RectF(48f, 1f, 52f, 5f), 1f)
+
+        decorHwcLayer.calculateTransparentRect()
+
+        assertThat(decorHwcLayer.transparentRect)
+            .isEqualTo(Rect(0, 15, decorHwcLayer.width, decorHwcLayer.height - 20))
+    }
+
+    @Test
+    fun testTransparentRegion_hasLongEdgeCutout_hasRoundedCorner_hasCutoutProtection() {
+        setupConfigs(cutoutRight, roundedSizeTop, roundedSizeBottom, RectF(48f, 1f, 52f, 5f), 1f)
+
+        decorHwcLayer.calculateTransparentRect()
+
+        assertThat(decorHwcLayer.transparentRect)
+            .isEqualTo(Rect(20, 5, decorHwcLayer.width - 20, decorHwcLayer.height))
+    }
+
+    private fun setupConfigs(
+        cutout: DisplayCutout?,
+        roundedTop: Int,
+        roundedBottom: Int,
+        protectionRect: RectF,
+        protectionProgress: Float
+    ) {
+        whenever(mockDisplay.getDisplayInfo(eq(decorHwcLayer.displayInfo))
+        ).then {
+            val info = it.getArgument<DisplayInfo>(0)
+            info.displayCutout = cutout
+            return@then true
+        }
+        decorHwcLayer.updateRoundedCornerSize(roundedTop, roundedBottom)
+        decorHwcLayer.protectionRect.set(protectionRect)
+        decorHwcLayer.cameraProtectionProgress = protectionProgress
+        decorHwcLayer.updateCutout()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 40632a8..7a0db1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -42,6 +42,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.SmartReplyController;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 import org.junit.After;
 import org.junit.AfterClass;
@@ -112,6 +113,11 @@
         // KeyguardUpdateMonitor to be created (injected).
         // TODO(b/1531701009) Clean up NotificationContentView creation to prevent this
         mDependency.injectMockDependency(SmartReplyController.class);
+
+        // Make sure that all tests on any SystemUIDialog does not crash because this dependency
+        // is missing (constructing the actual one would throw).
+        // TODO(b/219008720): Remove this.
+        mDependency.injectMockDependency(SystemUIDialogManager.class);
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/chooser/ChooserHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/chooser/ChooserHelperTest.java
deleted file mode 100644
index ecfb9ee..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/chooser/ChooserHelperTest.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.systemui.chooser;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyBoolean;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.Activity;
-import android.app.ActivityTaskManager;
-import android.content.Intent;
-import android.os.Binder;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class ChooserHelperTest extends SysuiTestCase {
-
-    @Test
-    public void testOnChoose_CallsStartActivityAsCallerWithToken() {
-        final Intent intent = new Intent();
-        final Binder token = new Binder();
-        intent.putExtra(ActivityTaskManager.EXTRA_PERMISSION_TOKEN, token);
-
-        final Activity mockActivity = mock(Activity.class);
-        when(mockActivity.getIntent()).thenReturn(intent);
-
-        ChooserHelper.onChoose(mockActivity);
-        verify(mockActivity, times(1)).startActivityAsCaller(
-                any(), any(), eq(token), anyBoolean(), anyInt());
-    }
-}
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
index 9908d44..da25c62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -16,15 +16,22 @@
 
 package com.android.systemui.controls.ui
 
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.provider.Settings
+import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.ControlsMetricsLogger
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.settings.SecureSettings
 import com.android.wm.shell.TaskViewFactory
+import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -44,7 +51,6 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class ControlActionCoordinatorImplTest : SysuiTestCase() {
-
     @Mock
     private lateinit var vibratorHelper: VibratorHelper
     @Mock
@@ -61,6 +67,10 @@
     private lateinit var cvh: ControlViewHolder
     @Mock
     private lateinit var metricsLogger: ControlsMetricsLogger
+    @Mock
+    private lateinit var secureSettings: SecureSettings
+    @Mock
+    private lateinit var mainHandler: Handler
 
     companion object {
         fun <T> any(): T = Mockito.any<T>()
@@ -75,16 +85,24 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
+        `when`(secureSettings.getUriFor(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS))
+                .thenReturn(Settings.Secure
+                        .getUriFor(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS))
+
         coordinator = spy(ControlActionCoordinatorImpl(
-            mContext,
-            bgExecutor,
-            uiExecutor,
-            activityStarter,
-            keyguardStateController,
-            taskViewFactory,
-            metricsLogger,
-            vibratorHelper
-        ))
+                mContext,
+                bgExecutor,
+                uiExecutor,
+                activityStarter,
+                keyguardStateController,
+                taskViewFactory,
+                metricsLogger,
+                vibratorHelper,
+                secureSettings,
+                mainHandler))
+
+        verify(secureSettings).registerContentObserver(any(Uri::class.java),
+                anyBoolean(), any(ContentObserver::class.java))
 
         `when`(cvh.cws.ci.controlId).thenReturn(ID)
         `when`(cvh.cws.control?.isAuthRequired()).thenReturn(true)
@@ -126,10 +144,23 @@
     fun testToggleRunsWhenLockedAndAuthNotRequired() {
         `when`(keyguardStateController.isShowing()).thenReturn(true)
         `when`(keyguardStateController.isUnlocked()).thenReturn(false)
-        `when`(cvh.cws.control?.isAuthRequired()).thenReturn(false)
+        doReturn(false).`when`(coordinator).isAuthRequired(
+                any(), anyBoolean())
 
         coordinator.toggle(cvh, "", true)
+
         verify(coordinator).bouncerOrRun(action, false /* authRequired */)
         verify(action).invoke()
     }
+
+    @Test
+    fun testIsAuthRequired() {
+        `when`(cvh.cws.control?.isAuthRequired).thenReturn(true)
+        assertThat(coordinator.isAuthRequired(cvh, false)).isTrue()
+
+        `when`(cvh.cws.control?.isAuthRequired).thenReturn(false)
+        assertThat(coordinator.isAuthRequired(cvh, false)).isTrue()
+
+        assertThat(coordinator.isAuthRequired(cvh, true)).isFalse()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index badafa4..f4b378e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -27,6 +27,7 @@
 import static com.android.systemui.doze.DozeMachine.State.FINISH;
 import static com.android.systemui.doze.DozeMachine.State.INITIALIZED;
 import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotSame;
@@ -468,37 +469,39 @@
     public void transitionToDoze_shouldClampBrightness_afterTimeout_clampsToDim() {
         when(mWakefulnessLifecycle.getLastSleepReason()).thenReturn(
                 PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
-        when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
         when(mDozeParameters.shouldClampToDimBrightness()).thenReturn(true);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mScreen.transitionTo(INITIALIZED, DOZE);
 
         // If we're dozing after a timeout, and playing the unlocked screen animation, we should
         // stay at or below dim brightness, because the screen dims just before timeout.
         assertTrue(mServiceFake.screenBrightness <= DIM_BRIGHTNESS);
+
+        // Once we transition to Doze, use the doze brightness
+        mScreen.transitionTo(INITIALIZED, DOZE);
+        assertEquals(mServiceFake.screenBrightness, DEFAULT_BRIGHTNESS);
     }
 
     @Test
     public void transitionToDoze_shouldClampBrightness_notAfterTimeout_doesNotClampToDim() {
         when(mWakefulnessLifecycle.getLastSleepReason()).thenReturn(
                 PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON);
-        when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
         when(mDozeParameters.shouldClampToDimBrightness()).thenReturn(true);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mScreen.transitionTo(INITIALIZED, DOZE);
 
         // If we're playing the unlocked screen off animation after a power button press, we should
         // leave the brightness alone.
         assertEquals(mServiceFake.screenBrightness, DEFAULT_BRIGHTNESS);
+
+        mScreen.transitionTo(INITIALIZED, DOZE);
+        assertEquals(mServiceFake.screenBrightness, DEFAULT_BRIGHTNESS);
     }
 
     @Test
     public void transitionToDoze_noClampBrightness_afterTimeout_noScreenOff_doesNotClampToDim() {
         when(mWakefulnessLifecycle.getLastSleepReason()).thenReturn(
                 PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
-        when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(false);
         when(mDozeParameters.shouldClampToDimBrightness()).thenReturn(false);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
@@ -514,11 +517,9 @@
                 PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
         when(mWakefulnessLifecycle.getWakefulness()).thenReturn(
                 WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP);
-        when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(false);
         when(mDozeParameters.shouldClampToDimBrightness()).thenReturn(false);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mScreen.transitionTo(INITIALIZED, DOZE);
 
         assertTrue(mServiceFake.screenBrightness <= DIM_BRIGHTNESS);
     }
@@ -529,7 +530,6 @@
                 PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON);
         when(mWakefulnessLifecycle.getWakefulness()).thenReturn(
                 WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP);
-        when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(false);
         when(mDozeParameters.shouldClampToDimBrightness()).thenReturn(false);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 7af039b..8ce10b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -17,7 +17,6 @@
 package com.android.systemui.dreams;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.eq;
@@ -118,31 +117,6 @@
     }
 
     @Test
-    public void testOnViewAttachedRegistersComputeInsetsListener() {
-        mController.onViewAttached();
-        verify(mViewTreeObserver).addOnComputeInternalInsetsListener(any());
-    }
-
-    @Test
-    public void testOnViewDetachedUnregistersComputeInsetsListener() {
-        mController.onViewDetached();
-        verify(mViewTreeObserver).removeOnComputeInternalInsetsListener(any());
-    }
-
-    @Test
-    public void testComputeInsetsListenerReturnsRegion() {
-        final ArgumentCaptor<ViewTreeObserver.OnComputeInternalInsetsListener>
-                computeInsetsListenerCapture =
-                ArgumentCaptor.forClass(ViewTreeObserver.OnComputeInternalInsetsListener.class);
-        mController.onViewAttached();
-        verify(mViewTreeObserver).addOnComputeInternalInsetsListener(
-                computeInsetsListenerCapture.capture());
-        final ViewTreeObserver.InternalInsetsInfo info = new ViewTreeObserver.InternalInsetsInfo();
-        computeInsetsListenerCapture.getValue().onComputeInternalInsets(info);
-        assertNotNull(info.touchableRegion);
-    }
-
-    @Test
     public void testBurnInProtectionStartsWhenContentViewAttached() {
         mController.onViewAttached();
         verify(mHandler).postDelayed(any(Runnable.class), eq(BURN_IN_PROTECTION_UPDATE_INTERVAL));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 58ffbfa..21768ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -38,6 +38,7 @@
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.complication.DreamPreviewComplication;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -95,6 +96,9 @@
     @Mock
     DreamOverlayStateController mStateController;
 
+    @Mock
+    DreamPreviewComplication mPreviewComplication;
+
     DreamOverlayService mService;
 
     @Before
@@ -119,7 +123,8 @@
         mService = new DreamOverlayService(mContext, mMainExecutor,
                 mDreamOverlayComponentFactory,
                 mStateController,
-                mKeyguardUpdateMonitor);
+                mKeyguardUpdateMonitor,
+                mPreviewComplication);
     }
 
     @Test
@@ -163,6 +168,31 @@
     }
 
     @Test
+    public void testPreviewModeFalseByDefault() {
+        mService.onBind(new Intent());
+
+        assertThat(mService.isPreviewMode()).isFalse();
+    }
+
+    @Test
+    public void testPreviewModeSetByIntentExtra() {
+        final Intent intent = new Intent();
+        intent.putExtra(DreamService.EXTRA_IS_PREVIEW, true);
+        mService.onBind(intent);
+
+        assertThat(mService.isPreviewMode()).isTrue();
+    }
+
+    @Test
+    public void testDreamLabel() {
+        final Intent intent = new Intent();
+        intent.putExtra(DreamService.EXTRA_DREAM_LABEL, "TestDream");
+        mService.onBind(intent);
+
+        assertThat(mService.getDreamLabel()).isEqualTo("TestDream");
+    }
+
+    @Test
     public void testDestroy() {
         mService.onDestroy();
         mMainExecutor.runAllReady();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index 515a1ac8..49da4bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -61,7 +61,7 @@
     }
 
     @Test
-    public void testStateChange() {
+    public void testStateChange_overlayActive() {
         final DreamOverlayStateController stateController = new DreamOverlayStateController(
                 mExecutor);
         stateController.addCallback(mCallback);
@@ -83,6 +83,38 @@
     }
 
     @Test
+    public void testStateChange_isPreviewMode() {
+        final DreamOverlayStateController stateController = new DreamOverlayStateController(
+                mExecutor);
+        stateController.addCallback(mCallback);
+        stateController.setPreviewMode(true);
+        mExecutor.runAllReady();
+
+        verify(mCallback).onStateChanged();
+        assertThat(stateController.isPreviewMode()).isTrue();
+
+        Mockito.clearInvocations(mCallback);
+        stateController.setPreviewMode(true);
+        mExecutor.runAllReady();
+        verify(mCallback, never()).onStateChanged();
+    }
+
+    @Test
+    public void testPreviewModeFalseByDefault() {
+        final DreamOverlayStateController stateController = new DreamOverlayStateController(
+                mExecutor);
+        assertThat(stateController.isPreviewMode()).isFalse();
+    }
+
+    @Test
+    public void testPreviewModeSetToTrue() {
+        final DreamOverlayStateController stateController = new DreamOverlayStateController(
+                mExecutor);
+        stateController.setPreviewMode(true);
+        assertThat(stateController.isPreviewMode()).isTrue();
+    }
+
+    @Test
     public void testCallback() {
         final DreamOverlayStateController stateController = new DreamOverlayStateController(
                 mExecutor);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index 7f72dda..ad8d44d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -29,8 +29,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.battery.BatteryMeterViewController;
-import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.touch.TouchInsetManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -46,37 +45,21 @@
     @Mock
     DreamOverlayStatusBarView mView;
     @Mock
-    BatteryController mBatteryController;
-    @Mock
-    BatteryMeterViewController mBatteryMeterViewController;
-    @Mock
     ConnectivityManager mConnectivityManager;
     @Mock
     NetworkCapabilities mNetworkCapabilities;
     @Mock
     Network mNetwork;
+    @Mock
+    TouchInsetManager.TouchInsetSession mTouchSession;
 
     DreamOverlayStatusBarViewController mController;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mController = new DreamOverlayStatusBarViewController(
-                mContext, mView, mBatteryController, mBatteryMeterViewController,
-                mConnectivityManager);
-    }
-
-    @Test
-    public void testOnInitInitializesControllers() {
-        mController.onInit();
-        verify(mBatteryMeterViewController).init();
-    }
-
-    @Test
-    public void testOnViewAttachedAddsBatteryControllerCallback() {
-        mController.onViewAttached();
-        verify(mBatteryController)
-                .addCallback(any(BatteryController.BatteryStateChangeCallback.class));
+        mController = new DreamOverlayStatusBarViewController(mView, mConnectivityManager,
+                mTouchSession);
     }
 
     @Test
@@ -113,13 +96,6 @@
     }
 
     @Test
-    public void testOnViewDetachedRemovesBatteryControllerCallback() {
-        mController.onViewDetached();
-        verify(mBatteryController)
-                .removeCallback(any(BatteryController.BatteryStateChangeCallback.class));
-    }
-
-    @Test
     public void testOnViewDetachedUnregistersNetworkCallback() {
         mController.onViewDetached();
         verify(mConnectivityManager)
@@ -127,26 +103,6 @@
     }
 
     @Test
-    public void testBatteryPercentTextShownWhenBatteryLevelChangesWhileCharging() {
-        final ArgumentCaptor<BatteryController.BatteryStateChangeCallback> callbackCapture =
-                ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback.class);
-        mController.onViewAttached();
-        verify(mBatteryController).addCallback(callbackCapture.capture());
-        callbackCapture.getValue().onBatteryLevelChanged(1, true, true);
-        verify(mView).showBatteryPercentText(true);
-    }
-
-    @Test
-    public void testBatteryPercentTextHiddenWhenBatteryLevelChangesWhileNotCharging() {
-        final ArgumentCaptor<BatteryController.BatteryStateChangeCallback> callbackCapture =
-                ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback.class);
-        mController.onViewAttached();
-        verify(mBatteryController).addCallback(callbackCapture.capture());
-        callbackCapture.getValue().onBatteryLevelChanged(1, true, false);
-        verify(mView).showBatteryPercentText(false);
-    }
-
-    @Test
     public void testWifiStatusHiddenWhenWifiBecomesAvailable() {
         // Make sure wifi starts out unavailable when onViewAttached is called.
         when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
index 64b267d..51dcf2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
@@ -29,6 +29,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.touch.TouchInsetManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -46,6 +47,9 @@
     @Mock
     ConstraintLayout mLayout;
 
+    @Mock
+    TouchInsetManager.TouchInsetSession mTouchSession;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -112,7 +116,8 @@
                 Complication.CATEGORY_STANDARD,
                 mLayout);
 
-        final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout, 0);
+        final ComplicationLayoutEngine engine =
+                new ComplicationLayoutEngine(mLayout, 0, mTouchSession);
         addComplication(engine, firstViewInfo);
 
         // Ensure the view is added to the top end corner
@@ -139,7 +144,8 @@
                 Complication.CATEGORY_STANDARD,
                 mLayout);
 
-        final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout, 0);
+        final ComplicationLayoutEngine engine =
+                new ComplicationLayoutEngine(mLayout, 0, mTouchSession);
         addComplication(engine, firstViewInfo);
 
         // Ensure the view is added to the top end corner
@@ -155,7 +161,8 @@
      */
     @Test
     public void testDirectionLayout() {
-        final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout, 0);
+        final ComplicationLayoutEngine engine =
+                new ComplicationLayoutEngine(mLayout, 0, mTouchSession);
 
         final ViewInfo firstViewInfo = new ViewInfo(
                 new ComplicationLayoutParams(
@@ -203,7 +210,8 @@
      */
     @Test
     public void testPositionLayout() {
-        final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout, 0);
+        final ComplicationLayoutEngine engine =
+                new ComplicationLayoutEngine(mLayout, 0, mTouchSession);
 
         final ViewInfo firstViewInfo = new ViewInfo(
                 new ComplicationLayoutParams(
@@ -290,7 +298,8 @@
     @Test
     public void testMargin() {
         final int margin = 5;
-        final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout, margin);
+        final ComplicationLayoutEngine engine =
+                new ComplicationLayoutEngine(mLayout, margin, mTouchSession);
 
         final ViewInfo firstViewInfo = new ViewInfo(
                 new ComplicationLayoutParams(
@@ -364,7 +373,8 @@
      */
     @Test
     public void testRemoval() {
-        final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout, 0);
+        final ComplicationLayoutEngine engine =
+                new ComplicationLayoutEngine(mLayout, 0, mTouchSession);
 
         final ViewInfo firstViewInfo = new ViewInfo(
                 new ComplicationLayoutParams(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
index 4cc5673..23a5b2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -20,7 +20,7 @@
 import android.content.Intent
 import android.content.pm.PackageManager.NameNotFoundException
 import android.content.res.Resources
-import androidx.test.filters.SmallTest
+import android.test.suitebuilder.annotation.SmallTest
 import com.android.internal.statusbar.IStatusBarService
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
@@ -35,6 +35,8 @@
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyString
 import org.mockito.Mockito.inOrder
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
@@ -57,6 +59,7 @@
     @Mock private lateinit var mFlagManager: FlagManager
     @Mock private lateinit var mMockContext: Context
     @Mock private lateinit var mSecureSettings: SecureSettings
+    @Mock private lateinit var mSystemProperties: SystemPropertiesHelper
     @Mock private lateinit var mResources: Resources
     @Mock private lateinit var mDumpManager: DumpManager
     @Mock private lateinit var mBarService: IStatusBarService
@@ -71,12 +74,13 @@
             mFlagManager,
             mMockContext,
             mSecureSettings,
+            mSystemProperties,
             mResources,
             mDumpManager,
             { mFlagMap },
             mBarService
         )
-        verify(mFlagManager).restartAction = any()
+        verify(mFlagManager).onSettingsChangedAction = any()
         mBroadcastReceiver = withArgCaptor {
             verify(mMockContext).registerReceiver(capture(), any(), nullable(), nullable(),
                 any())
@@ -123,6 +127,22 @@
     }
 
     @Test
+    fun testReadSysPropBooleanFlag() {
+        whenever(mSystemProperties.getBoolean(anyString(), anyBoolean())).thenAnswer {
+            if ("b".equals(it.getArgument<String?>(0))) {
+                return@thenAnswer true
+            }
+            return@thenAnswer it.getArgument(1)
+        }
+
+        assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(1, "a"))).isFalse()
+        assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(2, "b"))).isTrue()
+        assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(3, "c", true))).isTrue()
+        assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(4, "d", false))).isFalse()
+        assertThat(mFeatureFlagsDebug.isEnabled(SysPropBooleanFlag(5, "e"))).isFalse()
+    }
+
+    @Test
     fun testReadStringFlag() {
         whenever(mFlagManager.readFlagValue<String>(eq(3), any())).thenReturn("foo")
         whenever(mFlagManager.readFlagValue<String>(eq(4), any())).thenReturn("bar")
@@ -259,7 +279,7 @@
             verify(mFlagManager, times(numReads)).readFlagValue(eq(id), any<FlagSerializer<*>>())
             verify(mFlagManager).idToSettingsKey(eq(id))
             verify(mSecureSettings).putString(eq("key-$id"), eq(data))
-            verify(mFlagManager).dispatchListenersAndMaybeRestart(eq(id))
+            verify(mFlagManager).dispatchListenersAndMaybeRestart(eq(id), any())
         }.verifyNoMoreInteractions()
         verifyNoMoreInteractions(mFlagManager, mSecureSettings)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
index b5e6602..ad304c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
@@ -17,7 +17,7 @@
 
 import android.content.pm.PackageManager.NameNotFoundException
 import android.content.res.Resources
-import androidx.test.filters.SmallTest
+import android.test.suitebuilder.annotation.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.util.mockito.any
@@ -44,12 +44,13 @@
     private lateinit var mFeatureFlagsRelease: FeatureFlagsRelease
 
     @Mock private lateinit var mResources: Resources
+    @Mock private lateinit var mSystemProperties: SystemPropertiesHelper
     @Mock private lateinit var mDumpManager: DumpManager
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        mFeatureFlagsRelease = FeatureFlagsRelease(mResources, mDumpManager)
+        mFeatureFlagsRelease = FeatureFlagsRelease(mResources, mSystemProperties, mDumpManager)
     }
 
     @After
@@ -87,6 +88,17 @@
     }
 
     @Test
+    fun testSysPropBooleanFlag() {
+        val flagId = 213
+        val flagName = "sys_prop_flag"
+        val flagDefault = true
+
+        val flag = SysPropBooleanFlag(flagId, flagName, flagDefault)
+        whenever(mSystemProperties.getBoolean(flagName, flagDefault)).thenReturn(flagDefault)
+        assertThat(mFeatureFlagsRelease.isEnabled(flag)).isEqualTo(flagDefault)
+    }
+
+    @Test
     fun testDump() {
         val flag1 = BooleanFlag(1, true)
         val flag2 = ResourceBooleanFlag(2, 1002)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
index 644bd21..a2eca81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
@@ -19,7 +19,7 @@
 import android.database.ContentObserver
 import android.net.Uri
 import android.os.Handler
-import androidx.test.filters.SmallTest
+import android.test.suitebuilder.annotation.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -130,14 +130,14 @@
         mFlagManager.addListener(BooleanFlag(1, true), listener1)
         mFlagManager.addListener(BooleanFlag(10, true), listener10)
 
-        mFlagManager.dispatchListenersAndMaybeRestart(1)
+        mFlagManager.dispatchListenersAndMaybeRestart(1, null)
         val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
             verify(listener1).onFlagChanged(capture())
         }
         assertThat(flagEvent1.flagId).isEqualTo(1)
         verifyNoMoreInteractions(listener1, listener10)
 
-        mFlagManager.dispatchListenersAndMaybeRestart(10)
+        mFlagManager.dispatchListenersAndMaybeRestart(10, null)
         val flagEvent10 = withArgCaptor<FlagListenable.FlagEvent> {
             verify(listener10).onFlagChanged(capture())
         }
@@ -151,14 +151,14 @@
         mFlagManager.addListener(BooleanFlag(1, true), listener)
         mFlagManager.addListener(BooleanFlag(10, true), listener)
 
-        mFlagManager.dispatchListenersAndMaybeRestart(1)
+        mFlagManager.dispatchListenersAndMaybeRestart(1, null)
         val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
             verify(listener).onFlagChanged(capture())
         }
         assertThat(flagEvent1.flagId).isEqualTo(1)
         verifyNoMoreInteractions(listener)
 
-        mFlagManager.dispatchListenersAndMaybeRestart(10)
+        mFlagManager.dispatchListenersAndMaybeRestart(10, null)
         val flagEvent10 = withArgCaptor<FlagListenable.FlagEvent> {
             verify(listener, times(2)).onFlagChanged(capture())
         }
@@ -169,8 +169,7 @@
     @Test
     fun testRestartWithNoListeners() {
         val restartAction = mock<Consumer<Boolean>>()
-        mFlagManager.restartAction = restartAction
-        mFlagManager.dispatchListenersAndMaybeRestart(1)
+        mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
         verify(restartAction).accept(eq(false))
         verifyNoMoreInteractions(restartAction)
     }
@@ -178,11 +177,10 @@
     @Test
     fun testListenerCanSuppressRestart() {
         val restartAction = mock<Consumer<Boolean>>()
-        mFlagManager.restartAction = restartAction
         mFlagManager.addListener(BooleanFlag(1, true)) { event ->
             event.requestNoRestart()
         }
-        mFlagManager.dispatchListenersAndMaybeRestart(1)
+        mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
         verify(restartAction).accept(eq(true))
         verifyNoMoreInteractions(restartAction)
     }
@@ -190,11 +188,10 @@
     @Test
     fun testListenerOnlySuppressesRestartForOwnFlag() {
         val restartAction = mock<Consumer<Boolean>>()
-        mFlagManager.restartAction = restartAction
         mFlagManager.addListener(BooleanFlag(10, true)) { event ->
             event.requestNoRestart()
         }
-        mFlagManager.dispatchListenersAndMaybeRestart(1)
+        mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
         verify(restartAction).accept(eq(false))
         verifyNoMoreInteractions(restartAction)
     }
@@ -202,14 +199,13 @@
     @Test
     fun testRestartWhenNotAllListenersRequestSuppress() {
         val restartAction = mock<Consumer<Boolean>>()
-        mFlagManager.restartAction = restartAction
         mFlagManager.addListener(BooleanFlag(10, true)) { event ->
             event.requestNoRestart()
         }
         mFlagManager.addListener(BooleanFlag(10, true)) {
             // do not request
         }
-        mFlagManager.dispatchListenersAndMaybeRestart(1)
+        mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
         verify(restartAction).accept(eq(false))
         verifyNoMoreInteractions(restartAction)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 71fc8ee..953be7d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -19,7 +19,6 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
@@ -57,13 +56,11 @@
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.GlobalActions;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.telephony.TelephonyListenerManager;
@@ -112,7 +109,6 @@
     @Mock private UiEventLogger mUiEventLogger;
     @Mock private RingerModeTracker mRingerModeTracker;
     @Mock private RingerModeLiveData mRingerModeLiveData;
-    @Mock private SysUiState mSysUiState;
     @Mock private PackageManager mPackageManager;
     @Mock private Handler mHandler;
     @Mock private UserContextProvider mUserContextProvider;
@@ -120,7 +116,6 @@
     @Mock private StatusBar mStatusBar;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock private DialogLaunchAnimator mDialogLaunchAnimator;
-    @Mock private SystemUIDialogManager mDialogManager;
 
     private TestableLooper mTestableLooper;
 
@@ -161,19 +156,16 @@
                 mBackgroundExecutor,
                 mUiEventLogger,
                 mRingerModeTracker,
-                mSysUiState,
                 mHandler,
                 mPackageManager,
                 Optional.of(mStatusBar),
                 mKeyguardUpdateMonitor,
-                mDialogLaunchAnimator,
-                mDialogManager);
+                mDialogLaunchAnimator);
         mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting();
 
         ColorExtractor.GradientColors backdropColors = new ColorExtractor.GradientColors();
         backdropColors.setMainColor(Color.BLACK);
         when(mColorExtractor.getNeutralColors()).thenReturn(backdropColors);
-        when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
index e606be1..b359ae5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
@@ -33,10 +33,11 @@
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.FakeConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.animation.UniqueObjectHostView
-import junit.framework.Assert
+import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertNotNull
 import org.junit.Before
 import org.junit.Rule
@@ -44,16 +45,16 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyLong
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.`when`
-import org.mockito.Mockito.any
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.anyLong
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -83,8 +84,6 @@
     @Mock
     private lateinit var keyguardViewController: KeyguardViewController
     @Mock
-    private lateinit var configurationController: ConfigurationController
-    @Mock
     private lateinit var uniqueObjectHostView: UniqueObjectHostView
     @Mock
     private lateinit var dreamOverlayStateController: DreamOverlayStateController
@@ -97,6 +96,7 @@
     val mockito = MockitoJUnit.rule()
     private lateinit var mediaHiearchyManager: MediaHierarchyManager
     private lateinit var mediaFrame: ViewGroup
+    private val configurationController = FakeConfigurationController()
 
     @Before
     fun setup() {
@@ -176,12 +176,7 @@
 
     @Test
     fun testGoingToFullShade() {
-        // Let's set it onto Lock screen
-        `when`(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
-        `when`(notificationLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(
-            true)
-        statusBarCallback.value.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD)
-        clearInvocations(mediaCarouselController)
+        goToLockscreen()
 
         // Let's transition all the way to full shade
         mediaHiearchyManager.setTransitionToFullShadeAmount(100000f)
@@ -204,41 +199,48 @@
 
         // Let's make sure alpha is set
         mediaHiearchyManager.setTransitionToFullShadeAmount(2.0f)
-        Assert.assertTrue("alpha should not be 1.0f when cross fading", mediaFrame.alpha != 1.0f)
+        assertThat(mediaFrame.alpha).isNotEqualTo(1.0f)
     }
 
     @Test
     fun testTransformationOnLockScreenIsFading() {
-        // Let's set it onto Lock screen
-        `when`(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
-        `when`(notificationLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(
-            true)
-        statusBarCallback.value.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD)
-        clearInvocations(mediaCarouselController)
+        goToLockscreen()
+        expandQS()
 
-        // Let's transition from lockscreen to qs
-        mediaHiearchyManager.qsExpansion = 1.0f
         val transformType = mediaHiearchyManager.calculateTransformationType()
-        Assert.assertTrue("media isn't transforming to qs with a fade",
-            transformType == MediaHierarchyManager.TRANSFORMATION_TYPE_FADE)
+        assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE)
+    }
+
+    @Test
+    fun calculateTransformationType_onLockShade_inSplitShade_goingToFullShade_returnsTransition() {
+        enableSplitShade()
+        goToLockscreen()
+        expandQS()
+        mediaHiearchyManager.setTransitionToFullShadeAmount(10000f)
+
+        val transformType = mediaHiearchyManager.calculateTransformationType()
+        assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION)
+    }
+
+    @Test
+    fun calculateTransformationType_onLockShade_inSplitShade_notExpanding_returnsFade() {
+        enableSplitShade()
+        goToLockscreen()
+        goToLockedShade()
+        expandQS()
+        mediaHiearchyManager.setTransitionToFullShadeAmount(0f)
+
+        val transformType = mediaHiearchyManager.calculateTransformationType()
+        assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE)
     }
 
     @Test
     fun testTransformationOnLockScreenToQQSisFading() {
-        // Let's set it onto Lock screen
-        `when`(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
-        `when`(notificationLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(
-            true)
-        statusBarCallback.value.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD)
-        clearInvocations(mediaCarouselController)
+        goToLockscreen()
+        goToLockedShade()
 
-        // Let's transition from lockscreen to qs
-        `when`(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
-        statusBarCallback.value.onStatePreChange(StatusBarState.KEYGUARD,
-            StatusBarState.SHADE_LOCKED)
         val transformType = mediaHiearchyManager.calculateTransformationType()
-        Assert.assertTrue("media isn't transforming to qqswith a fade",
-            transformType == MediaHierarchyManager.TRANSFORMATION_TYPE_FADE)
+        assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE)
     }
 
     @Test
@@ -254,4 +256,32 @@
 
         verify(mediaCarouselController).closeGuts()
     }
-}
\ No newline at end of file
+
+    private fun enableSplitShade() {
+        context.getOrCreateTestableResources().addOverride(
+            R.bool.config_use_split_notification_shade, true
+        )
+        configurationController.notifyConfigurationChanged()
+    }
+
+    private fun goToLockscreen() {
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+        whenever(notificationLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(
+            true
+        )
+        statusBarCallback.value.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD)
+        clearInvocations(mediaCarouselController)
+    }
+
+    private fun goToLockedShade() {
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
+        statusBarCallback.value.onStatePreChange(
+            StatusBarState.KEYGUARD,
+            StatusBarState.SHADE_LOCKED
+        )
+    }
+
+    private fun expandQS() {
+        mediaHiearchyManager.qsExpansion = 1.0f
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index c5c4d79..2be30b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -45,7 +45,6 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -68,7 +67,6 @@
             mock(NotificationEntryManager.class);
     private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
     private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
-    private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class);
 
     private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl;
     private MediaOutputController mMediaOutputController;
@@ -82,7 +80,7 @@
     public void setUp() {
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
+                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
         mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext,
                 mMediaOutputController);
         mMediaOutputBaseDialogImpl.onCreate(new Bundle());
@@ -175,7 +173,7 @@
     class MediaOutputBaseDialogImpl extends MediaOutputBaseDialog {
 
         MediaOutputBaseDialogImpl(Context context, MediaOutputController mediaOutputController) {
-            super(context, mediaOutputController, mDialogManager);
+            super(context, mediaOutputController);
 
             mAdapter = mMediaOutputBaseAdapter;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index bdc3117..789822e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -55,7 +55,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -94,7 +93,6 @@
     private CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
     private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
     private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
-    private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class);
 
     private Context mSpyContext;
     private MediaOutputController mMediaOutputController;
@@ -117,7 +115,7 @@
 
         mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotifCollection, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
+                mNotifCollection, mUiEventLogger, mDialogLaunchAnimator);
         mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         MediaDescription.Builder builder = new MediaDescription.Builder();
@@ -161,7 +159,7 @@
     public void start_withoutPackageName_verifyMediaControllerInit() {
         mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotifCollection, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
+                mNotifCollection, mUiEventLogger, mDialogLaunchAnimator);
 
         mMediaOutputController.start(mCb);
 
@@ -182,7 +180,7 @@
     public void stop_withoutPackageName_verifyMediaControllerDeinit() {
         mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotifCollection, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
+                mNotifCollection, mUiEventLogger, mDialogLaunchAnimator);
 
         mMediaOutputController.start(mCb);
 
@@ -453,7 +451,7 @@
     public void getNotificationLargeIcon_withoutPackageName_returnsNull() {
         mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotifCollection, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
+                mNotifCollection, mUiEventLogger, mDialogLaunchAnimator);
 
         assertThat(mMediaOutputController.getNotificationIcon()).isNull();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index ada8d35..8a3ea56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -40,7 +40,6 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 import org.junit.After;
 import org.junit.Before;
@@ -68,7 +67,6 @@
             mock(NotificationEntryManager.class);
     private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
     private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
-    private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class);
 
     private MediaOutputDialog mMediaOutputDialog;
     private MediaOutputController mMediaOutputController;
@@ -78,10 +76,10 @@
     public void setUp() {
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
+                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         mMediaOutputDialog = new MediaOutputDialog(mContext, false,
-                mMediaOutputController, mUiEventLogger, mDialogManager);
+                mMediaOutputController, mUiEventLogger);
         mMediaOutputDialog.show();
 
         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice);
@@ -127,7 +125,7 @@
     // and verify if the calling times increases.
     public void onCreate_ShouldLogVisibility() {
         MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false,
-                mMediaOutputController, mUiEventLogger, mDialogManager);
+                mMediaOutputController, mUiEventLogger);
         testDialog.show();
 
         testDialog.dismissDialog();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
index b114452..e8cd6c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
@@ -38,7 +38,6 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 import org.junit.After;
 import org.junit.Before;
@@ -67,7 +66,6 @@
             mock(NotificationEntryManager.class);
     private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
     private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
-    private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class);
 
     private MediaOutputGroupDialog mMediaOutputGroupDialog;
     private MediaOutputController mMediaOutputController;
@@ -77,10 +75,10 @@
     public void setUp() {
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
+                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         mMediaOutputGroupDialog = new MediaOutputGroupDialog(mContext, false,
-                mMediaOutputController, mDialogManager);
+                mMediaOutputController);
         mMediaOutputGroupDialog.show();
         when(mLocalMediaManager.getSelectedMediaDevice()).thenReturn(mMediaDevices);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
index ea0a5a4..28de176 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.gesture.TapGestureDetector
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
@@ -50,6 +51,8 @@
     private lateinit var appIconDrawable: Drawable
     @Mock
     private lateinit var windowManager: WindowManager
+    @Mock
+    private lateinit var tapGestureDetector: TapGestureDetector
 
     @Before
     fun setUp() {
@@ -58,23 +61,28 @@
         fakeClock = FakeSystemClock()
         fakeExecutor = FakeExecutor(fakeClock)
 
-        controllerCommon = TestControllerCommon(context, windowManager, fakeExecutor)
+        controllerCommon = TestControllerCommon(
+            context, windowManager, fakeExecutor, tapGestureDetector
+        )
     }
 
     @Test
-    fun displayChip_chipAdded() {
+    fun displayChip_chipAddedAndGestureDetectionStarted() {
         controllerCommon.displayChip(getState())
 
         verify(windowManager).addView(any(), any())
+        verify(tapGestureDetector).addOnGestureDetectedCallback(any(), any())
     }
 
     @Test
-    fun displayChip_twice_chipNotAddedTwice() {
+    fun displayChip_twice_chipAndGestureDetectionNotAddedTwice() {
         controllerCommon.displayChip(getState())
         reset(windowManager)
+        reset(tapGestureDetector)
 
         controllerCommon.displayChip(getState())
         verify(windowManager, never()).addView(any(), any())
+        verify(tapGestureDetector, never()).addOnGestureDetectedCallback(any(), any())
     }
 
     @Test
@@ -130,7 +138,7 @@
     }
 
     @Test
-    fun removeChip_chipRemoved() {
+    fun removeChip_chipRemovedAndGestureDetectionStopped() {
         // First, add the chip
         controllerCommon.displayChip(getState())
 
@@ -138,6 +146,7 @@
         controllerCommon.removeChip()
 
         verify(windowManager).removeView(any())
+        verify(tapGestureDetector).removeOnGestureDetectedCallback(any())
     }
 
     @Test
@@ -174,8 +183,9 @@
         context: Context,
         windowManager: WindowManager,
         @Main mainExecutor: DelayableExecutor,
-        ) : MediaTttChipControllerCommon<MediaTttChipState>(
-        context, windowManager, mainExecutor, R.layout.media_ttt_chip
+        tapGestureDetector: TapGestureDetector,
+    ) : MediaTttChipControllerCommon<MediaTttChipState>(
+        context, windowManager, mainExecutor, tapGestureDetector, R.layout.media_ttt_chip
     ) {
         override fun updateChipView(chipState: MediaTttChipState, currentChipView: ViewGroup) {
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index 117a6c8..e5f4df6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -22,6 +22,8 @@
 import android.graphics.drawable.Drawable
 import android.media.MediaRoute2Info
 import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
 import android.view.View
 import android.view.ViewGroup
 import android.view.WindowManager
@@ -30,6 +32,7 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.gesture.TapGestureDetector
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -37,6 +40,7 @@
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
 import org.mockito.Mockito.never
@@ -45,6 +49,8 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
 class MediaTttChipControllerReceiverTest : SysuiTestCase() {
     private lateinit var controllerReceiver: MediaTttChipControllerReceiver
 
@@ -76,7 +82,8 @@
             context,
             windowManager,
             FakeExecutor(FakeSystemClock()),
-            Handler.getMain()
+            TapGestureDetector(context),
+            Handler.getMain(),
         )
 
         val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index b440064..e5ba3f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -21,6 +21,8 @@
 import android.content.pm.PackageManager
 import android.graphics.drawable.Drawable
 import android.media.MediaRoute2Info
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
 import android.view.View
 import android.view.WindowManager
 import android.widget.ImageView
@@ -31,6 +33,7 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.gesture.TapGestureDetector
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -38,6 +41,7 @@
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
 import org.mockito.Mockito.never
@@ -46,6 +50,8 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
 class MediaTttChipControllerSenderTest : SysuiTestCase() {
     private lateinit var controllerSender: MediaTttChipControllerSender
 
@@ -73,7 +79,11 @@
         context.setMockPackageManager(packageManager)
 
         controllerSender = MediaTttChipControllerSender(
-            commandQueue, context, windowManager, FakeExecutor(FakeSystemClock())
+            commandQueue,
+            context,
+            windowManager,
+            FakeExecutor(FakeSystemClock()),
+            TapGestureDetector(context)
         )
 
         val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
index 8ce50a6..f736f26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
@@ -100,7 +100,9 @@
 
     @After
     fun tearDown() {
-        ViewUtils.detachView(view)
+        if (view.isAttachedToWindow) {
+            ViewUtils.detachView(view)
+        }
     }
 
     @Test
@@ -139,8 +141,7 @@
 
     @Test
     fun testMultiUserSwitchUpdatedWhenSettingChanged() {
-        // When expanded, listening is true
-        controller.setListening(true)
+        // Always listening to setting while View is attached
         testableLooper.processAllMessages()
 
         val multiUserSwitch = view.requireViewById<View>(R.id.multi_user_switch)
@@ -156,4 +157,24 @@
 
         assertThat(multiUserSwitch.visibility).isEqualTo(View.VISIBLE)
     }
+
+    @Test
+    fun testMultiUserSettingNotListenedAfterDetach() {
+        testableLooper.processAllMessages()
+
+        val multiUserSwitch = view.requireViewById<View>(R.id.multi_user_switch)
+        assertThat(multiUserSwitch.visibility).isNotEqualTo(View.VISIBLE)
+
+        ViewUtils.detachView(view)
+
+        // The setting is only used as an indicator for whether the view should refresh. The actual
+        // value of the setting is ignored; isMultiUserEnabled is the source of truth
+        whenever(multiUserSwitchController.isMultiUserEnabled).thenReturn(true)
+
+        // Changing the value of USER_SWITCHER_ENABLED should cause the view to update
+        fakeSettings.putIntForUser(Settings.Global.USER_SWITCHER_ENABLED, 1, userTracker.userId)
+        testableLooper.processAllMessages()
+
+        assertThat(multiUserSwitch.visibility).isNotEqualTo(View.VISIBLE)
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index ea4d7cc..c5bc68d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -45,6 +45,7 @@
 import android.metrics.LogMaker;
 import android.os.Handler;
 import android.os.Looper;
+import android.provider.Settings;
 import android.service.quicksettings.Tile;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -58,6 +59,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.ActivityStarter;
@@ -101,6 +103,7 @@
     private StatusBarStateController mStatusBarStateController;
     @Mock
     private ActivityStarter mActivityStarter;
+
     private UiEventLoggerFake mUiEventLoggerFake;
     private InstanceId mInstanceId = InstanceId.fakeInstanceId(5);
 
@@ -113,7 +116,7 @@
         mTestableLooper = TestableLooper.get(this);
         mUiEventLoggerFake = new UiEventLoggerFake();
         when(mHost.indexOf(SPEC)).thenReturn(POSITION);
-        when(mHost.getContext()).thenReturn(mContext.getBaseContext());
+        when(mHost.getContext()).thenReturn(mContext);
         when(mHost.getUiEventLogger()).thenReturn(mUiEventLoggerFake);
         when(mHost.getNewInstanceId()).thenReturn(mInstanceId);
 
@@ -342,6 +345,22 @@
         mTestableLooper.processAllMessages();
     }
 
+    @Test
+    public void testClickOnDisabledByPolicyDoesntClickLaunchesIntent() {
+        String restriction = "RESTRICTION";
+        mTile.getState().disabledByPolicy = true;
+        EnforcedAdmin admin = EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(restriction);
+        mTile.setEnforcedAdmin(admin);
+
+        mTile.click(null);
+        mTestableLooper.processAllMessages();
+        assertFalse(mTile.mClicked);
+
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        verify(mActivityStarter).postStartActivityDismissingKeyguard(captor.capture(), anyInt());
+        assertEquals(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS, captor.getValue().getAction());
+    }
+
     private void assertEvent(UiEventLogger.UiEventEnum eventType,
             UiEventLoggerFake.FakeUiEvent fakeEvent) {
         assertEquals(eventType.getId(), fakeEvent.eventId);
@@ -400,6 +419,10 @@
             getState().state = Tile.STATE_ACTIVE;
         }
 
+        public void setEnforcedAdmin(EnforcedAdmin admin) {
+            mEnforcedAdmin = admin;
+        }
+
         @Override
         public BooleanState newTileState() {
             return new BooleanState();
@@ -412,7 +435,6 @@
 
         @Override
         protected void handleUpdateState(BooleanState state, Object arg) {
-
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
new file mode 100644
index 0000000..cc47248
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -0,0 +1,117 @@
+package com.android.systemui.qs.tiles
+
+import android.content.Context
+import android.os.Handler
+import android.os.Looper
+import android.os.UserManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.statusbar.policy.BluetoothController
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class BluetoothTileTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var mockContext: Context
+    @Mock
+    private lateinit var qsLogger: QSLogger
+    @Mock
+    private lateinit var qsHost: QSTileHost
+    @Mock
+    private lateinit var metricsLogger: MetricsLogger
+    private val falsingManager = FalsingManagerFake()
+    @Mock
+    private lateinit var statusBarStateController: StatusBarStateController
+    @Mock
+    private lateinit var activityStarter: ActivityStarter
+    @Mock
+    private lateinit var bluetoothController: BluetoothController
+
+    private val uiEventLogger = UiEventLoggerFake()
+    private lateinit var testableLooper: TestableLooper
+    private lateinit var tile: FakeBluetoothTile
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        testableLooper = TestableLooper.get(this)
+
+        Mockito.`when`(qsHost.context).thenReturn(mockContext)
+        Mockito.`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
+
+        tile = FakeBluetoothTile(
+            qsHost,
+            testableLooper.looper,
+            Handler(testableLooper.looper),
+            falsingManager,
+            metricsLogger,
+            statusBarStateController,
+            activityStarter,
+            qsLogger,
+            bluetoothController
+        )
+
+        tile.initialize()
+        testableLooper.processAllMessages()
+    }
+
+    @Test
+    fun testRestrictionChecked() {
+        tile.refreshState()
+        testableLooper.processAllMessages()
+
+        assertThat(tile.restrictionChecked).isEqualTo(UserManager.DISALLOW_BLUETOOTH)
+    }
+
+    private class FakeBluetoothTile(
+        qsTileHost: QSTileHost,
+        backgroundLooper: Looper,
+        mainHandler: Handler,
+        falsingManager: FalsingManager,
+        metricsLogger: MetricsLogger,
+        statusBarStateController: StatusBarStateController,
+        activityStarter: ActivityStarter,
+        qsLogger: QSLogger,
+        bluetoothController: BluetoothController
+    ) : BluetoothTile(
+        qsTileHost,
+        backgroundLooper,
+        mainHandler,
+        falsingManager,
+        metricsLogger,
+        statusBarStateController,
+        activityStarter,
+        qsLogger,
+        bluetoothController
+    ) {
+        var restrictionChecked: String? = null
+
+        override fun checkIfRestrictionEnforcedByAdminOnly(
+            state: QSTile.State?,
+            userRestriction: String?
+        ) {
+            restrictionChecked = userRestriction
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 9076e16..75ccd8f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -23,7 +23,7 @@
 import com.android.systemui.statusbar.phone.NotificationPanelViewController
 import com.android.systemui.statusbar.phone.ScrimController
 import com.android.systemui.statusbar.phone.StatusBar
-import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.FakeConfigurationController
 import org.junit.After
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertNotNull
@@ -66,7 +66,6 @@
     @Mock lateinit var wakefulnessLifecycle: WakefulnessLifecycle
     @Mock lateinit var mediaHierarchyManager: MediaHierarchyManager
     @Mock lateinit var scrimController: ScrimController
-    @Mock lateinit var configurationController: ConfigurationController
     @Mock lateinit var falsingManager: FalsingManager
     @Mock lateinit var notificationPanelController: NotificationPanelViewController
     @Mock lateinit var nsslController: NotificationStackScrollLayoutController
@@ -77,6 +76,8 @@
     @Mock lateinit var qS: QS
     @JvmField @Rule val mockito = MockitoJUnit.rule()
 
+    private val configurationController = FakeConfigurationController()
+
     @Before
     fun setup() {
         val helper = NotificationTestHelper(
@@ -244,4 +245,27 @@
         verify(qS).setTransitionToFullShadeAmount(anyFloat(), anyFloat())
         verify(depthController).transitionToFullShadeProgress = anyFloat()
     }
+
+    @Test
+    fun setDragDownAmount_setsValueOnMediaHierarchyManager() {
+        transitionController.dragDownAmount = 10f
+
+        verify(mediaHierarchyManager).setTransitionToFullShadeAmount(10f)
+    }
+
+    @Test
+    fun setDragDownAmount_inSplitShade_setsValueOnMediaHierarchyManager() {
+        enableSplitShade()
+
+        transitionController.dragDownAmount = 10f
+
+        verify(mediaHierarchyManager).setTransitionToFullShadeAmount(10f)
+    }
+
+    private fun enableSplitShade() {
+        context.getOrCreateTestableResources().addOverride(
+            R.bool.config_use_split_notification_shade, true
+        )
+        configurationController.notifyConfigurationChanged()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 7fafb24..407044b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -54,7 +54,6 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -121,7 +120,6 @@
                 mock(KeyguardBypassController.class),
                 Optional.of(mock(Bubbles.class)),
                 mock(DynamicPrivacyController.class),
-                mock(ForegroundServiceSectionController.class),
                 mock(DynamicChildBindController.class),
                 mock(LowPriorityInflationHelper.class),
                 mock(AssistantFeedbackController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt
new file mode 100644
index 0000000..c038903
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt
@@ -0,0 +1,130 @@
+package com.android.systemui.statusbar.gesture
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.InputEvent
+import android.view.MotionEvent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class GenericGestureDetectorTest : SysuiTestCase() {
+
+    private lateinit var gestureDetector: TestGestureDetector
+
+    @Before
+    fun setUp() {
+        gestureDetector = TestGestureDetector()
+    }
+
+    @Test
+    fun noCallbacksRegistered_notGestureListening() {
+        assertThat(gestureDetector.isGestureListening).isFalse()
+    }
+
+    @Test
+    fun callbackRegistered_isGestureListening() {
+        gestureDetector.addOnGestureDetectedCallback("tag"){}
+
+        assertThat(gestureDetector.isGestureListening).isTrue()
+    }
+
+    @Test
+    fun multipleCallbacksRegistered_isGestureListening() {
+        gestureDetector.addOnGestureDetectedCallback("tag"){}
+        gestureDetector.addOnGestureDetectedCallback("tag2"){}
+
+        assertThat(gestureDetector.isGestureListening).isTrue()
+    }
+
+    @Test
+    fun allCallbacksUnregistered_notGestureListening() {
+        gestureDetector.addOnGestureDetectedCallback("tag"){}
+        gestureDetector.addOnGestureDetectedCallback("tag2"){}
+
+        gestureDetector.removeOnGestureDetectedCallback("tag")
+        gestureDetector.removeOnGestureDetectedCallback("tag2")
+
+        assertThat(gestureDetector.isGestureListening).isFalse()
+    }
+
+    @Test
+    fun someButNotAllCallbacksUnregistered_isGestureListening() {
+        gestureDetector.addOnGestureDetectedCallback("tag"){}
+        gestureDetector.addOnGestureDetectedCallback("tag2"){}
+
+        gestureDetector.removeOnGestureDetectedCallback("tag2")
+
+        assertThat(gestureDetector.isGestureListening).isTrue()
+    }
+
+    @Test
+    fun onInputEvent_meetsGestureCriteria_allCallbacksNotified() {
+        var callbackNotified = false
+        gestureDetector.addOnGestureDetectedCallback("tag"){
+            callbackNotified = true
+        }
+
+        gestureDetector.onInputEvent(
+            MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, CORRECT_X, 0f, 0)
+        )
+
+        assertThat(callbackNotified).isTrue()
+    }
+
+    @Test
+    fun onInputEvent_doesNotMeetGestureCriteria_callbackNotNotified() {
+        var callbackNotified = false
+        gestureDetector.addOnGestureDetectedCallback("tag"){
+            callbackNotified = true
+        }
+
+        gestureDetector.onInputEvent(
+            MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, CORRECT_X - 5, 0f, 0)
+        )
+
+        assertThat(callbackNotified).isFalse()
+    }
+
+    @Test
+    fun callbackUnregisteredThenGestureDetected_oldCallbackNotNotified() {
+        var oldCallbackNotified = false
+        gestureDetector.addOnGestureDetectedCallback("tag"){
+            oldCallbackNotified = true
+        }
+        gestureDetector.addOnGestureDetectedCallback("tag2"){}
+
+        gestureDetector.removeOnGestureDetectedCallback("tag")
+        gestureDetector.onInputEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, CORRECT_X, 0f, 0))
+
+        assertThat(oldCallbackNotified).isFalse()
+    }
+
+    inner class TestGestureDetector : GenericGestureDetector("fakeTag") {
+        var isGestureListening = false
+
+        override fun onInputEvent(ev: InputEvent) {
+            if (ev is MotionEvent && ev.x == CORRECT_X) {
+                onGestureDetected()
+            }
+        }
+
+        override fun startGestureListening() {
+            super.startGestureListening()
+            isGestureListening = true
+        }
+
+        override fun stopGestureListening() {
+            super.stopGestureListening()
+            isGestureListening = false
+        }
+    }
+}
+
+private const val CORRECT_X = 1234f
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index f2b7bf5..0fff5f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -201,7 +201,6 @@
                 () -> mNotificationRowBinder,
                 () -> mRemoteInputManager,
                 mLeakDetector,
-                mock(ForegroundServiceDismissalFeatureController.class),
                 mock(IStatusBarService.class),
                 NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(),
                 mock(DumpManager.class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index 52189e4..7fc5ece 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -61,7 +61,6 @@
 import com.android.systemui.statusbar.SbnBuilder;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
-import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationClicker;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -191,7 +190,6 @@
                 () -> mRowBinder,
                 () -> mRemoteInputManager,
                 mLeakDetector,
-                mock(ForegroundServiceDismissalFeatureController.class),
                 mock(IStatusBarService.class),
                 NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(),
                 mock(DumpManager.class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index c4f954f..6b93ae2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -58,7 +58,6 @@
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
@@ -68,7 +67,6 @@
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.ForegroundServiceDungeonView;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
@@ -129,9 +127,6 @@
     @Mock private IStatusBarService mIStatusBarService;
     @Mock private UiEventLogger mUiEventLogger;
     @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
-    @Mock private ForegroundServiceDismissalFeatureController mFgFeatureController;
-    @Mock private ForegroundServiceSectionController mFgServicesSectionController;
-    @Mock private ForegroundServiceDungeonView mForegroundServiceDungeonView;
     @Mock private LayoutInflater mLayoutInflater;
     @Mock private NotificationRemoteInputManager mRemoteInputManager;
     @Mock private VisualStabilityManager mVisualStabilityManager;
@@ -151,8 +146,6 @@
 
         when(mNotificationSwipeHelperBuilder.build()).thenReturn(mNotificationSwipeHelper);
         when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
-        when(mFgServicesSectionController.createView(mLayoutInflater))
-                .thenReturn(mForegroundServiceDungeonView);
 
         mController = new NotificationStackScrollLayoutController(
                 true,
@@ -187,8 +180,6 @@
                 mLockscreenShadeTransitionController,
                 mIStatusBarService,
                 mUiEventLogger,
-                mFgFeatureController,
-                mFgServicesSectionController,
                 mLayoutInflater,
                 mRemoteInputManager,
                 mVisualStabilityManager,
@@ -399,22 +390,6 @@
     }
 
     @Test
-    public void testForegroundDismissEnabled() {
-        when(mFgFeatureController.isForegroundServiceDismissalEnabled()).thenReturn(true);
-        mController.attach(mNotificationStackScrollLayout);
-        verify(mNotificationStackScrollLayout).initializeForegroundServiceSection(
-                mForegroundServiceDungeonView);
-    }
-
-    @Test
-    public void testForegroundDismissaDisabled() {
-        when(mFgFeatureController.isForegroundServiceDismissalEnabled()).thenReturn(false);
-        mController.attach(mNotificationStackScrollLayout);
-        verify(mNotificationStackScrollLayout, never()).initializeForegroundServiceSection(
-                any(ForegroundServiceDungeonView.class));
-    }
-
-    @Test
     public void testUpdateFooter_remoteInput() {
         ArgumentCaptor<RemoteInputController.Callback> callbackCaptor =
                 ArgumentCaptor.forClass(RemoteInputController.Callback.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index a14ea54..5f2bbd3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -27,6 +27,7 @@
 
 import android.content.res.Resources;
 import android.hardware.display.AmbientDisplayConfiguration;
+import android.os.Handler;
 import android.os.PowerManager;
 import android.provider.Settings;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -61,6 +62,7 @@
 public class DozeParametersTest extends SysuiTestCase {
     private DozeParameters mDozeParameters;
 
+    @Mock Handler mHandler;
     @Mock Resources mResources;
     @Mock private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
     @Mock private AlwaysOnDisplayPolicy mAlwaysOnDisplayPolicy;
@@ -102,6 +104,8 @@
                 .thenReturn(mFoldAodAnimationController);
 
         mDozeParameters = new DozeParameters(
+            mContext,
+            mHandler,
             mResources,
             mAmbientDisplayConfiguration,
             mAlwaysOnDisplayPolicy,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
index 671ab59..c797bc8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
@@ -32,6 +32,8 @@
 import static org.mockito.Mockito.when;
 
 import android.app.IActivityManager;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.View;
@@ -228,6 +230,36 @@
     }
 
     @Test
+    public void rotationBecameAllowed_layoutParamsUpdated() {
+        mNotificationShadeWindowController.setKeyguardShowing(true);
+        when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false);
+        mNotificationShadeWindowController.onConfigChanged(new Configuration());
+        clearInvocations(mWindowManager);
+
+        when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(true);
+        mNotificationShadeWindowController.onConfigChanged(new Configuration());
+
+        verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
+        assertThat(mLayoutParameters.getValue().screenOrientation)
+                .isEqualTo(ActivityInfo.SCREEN_ORIENTATION_USER);
+    }
+
+    @Test
+    public void rotationBecameNotAllowed_layoutParamsUpdated() {
+        mNotificationShadeWindowController.setKeyguardShowing(true);
+        when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(true);
+        mNotificationShadeWindowController.onConfigChanged(new Configuration());
+        clearInvocations(mWindowManager);
+
+        when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false);
+        mNotificationShadeWindowController.onConfigChanged(new Configuration());
+
+        verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
+        assertThat(mLayoutParameters.getValue().screenOrientation)
+                .isEqualTo(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
+    }
+
+    @Test
     public void batchApplyWindowLayoutParams_doesNotDispatchEvents() {
         mNotificationShadeWindowController.setForceDozeBrightness(true);
         verify(mWindowManager).updateViewLayout(any(), any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index c7db9e4..8610936 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -381,7 +381,7 @@
         mShadeController = new ShadeControllerImpl(mCommandQueue,
                 mStatusBarStateController, mNotificationShadeWindowController,
                 mStatusBarKeyguardViewManager, mContext.getSystemService(WindowManager.class),
-                () -> Optional.of(mStatusBar), () -> mAssistManager, Optional.of(mBubbles));
+                () -> Optional.of(mStatusBar), () -> mAssistManager);
 
         when(mOperatorNameViewControllerFactory.create(any()))
                 .thenReturn(mOperatorNameViewController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index 0920cac..1c48eca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -105,6 +105,7 @@
         val notificationCollection = mock(CommonNotifCollection::class.java)
 
         controller = OngoingCallController(
+                context,
                 notificationCollection,
                 mockOngoingCallFlags,
                 clock,
@@ -204,17 +205,48 @@
 
     /** Regression test for b/194731244. */
     @Test
-    fun onEntryUpdated_calledManyTimes_uidObserverUnregisteredManyTimes() {
-        val numCalls = 4
-
-        for (i in 0 until numCalls) {
+    fun onEntryUpdated_calledManyTimes_uidObserverOnlyRegisteredOnce() {
+        for (i in 0 until 4) {
             // Re-create the notification each time so that it's considered a different object and
-            // observers will get re-registered (and hopefully unregistered).
+            // will re-trigger the whole flow.
             notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
         }
 
-        // There should be 1 observer still registered, so we should unregister n-1 times.
-        verify(mockIActivityManager, times(numCalls - 1)).unregisterUidObserver(any())
+        verify(mockIActivityManager, times(1))
+            .registerUidObserver(any(), any(), any(), any())
+    }
+
+    /** Regression test for b/216248574. */
+    @Test
+    fun entryUpdated_getUidProcessStateThrowsException_noCrash() {
+        `when`(mockIActivityManager.getUidProcessState(eq(CALL_UID), nullable(String::class.java)))
+                .thenThrow(SecurityException())
+
+        // No assert required, just check no crash
+        notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+    }
+
+    /** Regression test for b/216248574. */
+    @Test
+    fun entryUpdated_registerUidObserverThrowsException_noCrash() {
+        `when`(mockIActivityManager.registerUidObserver(
+            any(), any(), any(), nullable(String::class.java)
+        )).thenThrow(SecurityException())
+
+        // No assert required, just check no crash
+        notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+    }
+
+    /** Regression test for b/216248574. */
+    @Test
+    fun entryUpdated_packageNameProvidedToActivityManager() {
+        notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+        val packageNameCaptor = ArgumentCaptor.forClass(String::class.java)
+        verify(mockIActivityManager).registerUidObserver(
+            any(), any(), any(), packageNameCaptor.capture()
+        )
+        assertThat(packageNameCaptor.value).isNotNull()
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
new file mode 100644
index 0000000..3a5d9ee
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
@@ -0,0 +1,31 @@
+package com.android.systemui.statusbar.policy
+
+import android.content.res.Configuration
+
+/** Fake implementation of [ConfigurationController] for tests. */
+class FakeConfigurationController : ConfigurationController {
+
+    private var listener: ConfigurationController.ConfigurationListener? = null
+
+    override fun addCallback(listener: ConfigurationController.ConfigurationListener) {
+        this.listener = listener
+    }
+
+    override fun removeCallback(listener: ConfigurationController.ConfigurationListener) {
+        this.listener = null
+    }
+
+    override fun onConfigurationChanged(newConfiguration: Configuration?) {
+        listener?.onConfigChanged(newConfiguration)
+    }
+
+    override fun notifyThemeChanged() {
+        listener?.onThemeChanged()
+    }
+
+    fun notifyConfigurationChanged() {
+        onConfigurationChanged(newConfiguration = null)
+    }
+
+    override fun isLayoutRtl(): Boolean = false
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java
new file mode 100644
index 0000000..14b9bfb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.touch;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.view.ViewRootImpl;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class TouchInsetManagerTest extends SysuiTestCase {
+    @Mock
+    private View mRootView;
+
+    @Mock
+    private ViewRootImpl mRootViewImpl;
+
+    private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mRootView.getViewRootImpl()).thenReturn(mRootViewImpl);
+    }
+
+    @Test
+    public void testRootViewOnAttachedHandling() {
+        // Create inset manager
+        final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
+                mRootView);
+
+        final ArgumentCaptor<View.OnAttachStateChangeListener> listener =
+                ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+
+        // Ensure manager has registered to listen to attached state of root view.
+        verify(mRootView).addOnAttachStateChangeListener(listener.capture());
+
+        // Trigger attachment and verify touchable region is set.
+        listener.getValue().onViewAttachedToWindow(mRootView);
+        verify(mRootViewImpl).setTouchableRegion(any());
+    }
+
+    @Test
+    public void testInsetRegionPropagation() {
+        // Create inset manager
+        final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
+                mRootView);
+
+        // Create session
+        final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
+
+        // Add a view to the session.
+        final Rect rect = new Rect(0, 0, 2, 2);
+
+        session.addViewToTracking(createView(rect));
+        mFakeExecutor.runAllReady();
+
+        // Check to see if view was properly accounted for.
+        final Region expectedRegion = Region.obtain();
+        expectedRegion.op(rect, Region.Op.UNION);
+        verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
+    }
+
+    @Test
+    public void testMultipleRegions() {
+        // Create inset manager
+        final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
+                mRootView);
+
+        // Create session
+        final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
+
+        // Add a view to the session.
+        final Rect firstBounds = new Rect(0, 0, 2, 2);
+        session.addViewToTracking(createView(firstBounds));
+
+        mFakeExecutor.runAllReady();
+        clearInvocations(mRootViewImpl);
+
+        // Create second session
+        final TouchInsetManager.TouchInsetSession secondSession = insetManager.createSession();
+
+        // Add a view to the second session.
+        final Rect secondBounds = new Rect(4, 4, 8, 10);
+        secondSession.addViewToTracking(createView(secondBounds));
+
+        mFakeExecutor.runAllReady();
+
+        // Check to see if all views and sessions was properly accounted for.
+        {
+            final Region expectedRegion = Region.obtain();
+            expectedRegion.op(firstBounds, Region.Op.UNION);
+            expectedRegion.op(secondBounds, Region.Op.UNION);
+            verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
+        }
+
+
+        clearInvocations(mRootViewImpl);
+
+        // clear first session, ensure second session is still reflected.
+        session.clear();
+        mFakeExecutor.runAllReady();
+        {
+            final Region expectedRegion = Region.obtain();
+            expectedRegion.op(firstBounds, Region.Op.UNION);
+            verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
+        }
+    }
+
+    @Test
+    public void testMultipleViews() {
+        // Create inset manager
+        final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
+                mRootView);
+
+        // Create session
+        final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
+
+        // Add a view to the session.
+        final Rect firstViewBounds = new Rect(0, 0, 2, 2);
+        session.addViewToTracking(createView(firstViewBounds));
+
+        // only capture second invocation.
+        mFakeExecutor.runAllReady();
+        clearInvocations(mRootViewImpl);
+
+        // Add a second view to the session
+        final Rect secondViewBounds = new Rect(4, 4, 9, 10);
+        final View secondView = createView(secondViewBounds);
+        session.addViewToTracking(secondView);
+
+        mFakeExecutor.runAllReady();
+
+        // Check to see if all views and sessions was properly accounted for.
+        {
+            final Region expectedRegion = Region.obtain();
+            expectedRegion.op(firstViewBounds, Region.Op.UNION);
+            expectedRegion.op(secondViewBounds, Region.Op.UNION);
+            verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
+        }
+
+        // Remove second view.
+        session.removeViewFromTracking(secondView);
+
+        clearInvocations(mRootViewImpl);
+        mFakeExecutor.runAllReady();
+
+        // Ensure first view still reflected in touch region.
+        {
+            final Region expectedRegion = Region.obtain();
+            expectedRegion.op(firstViewBounds, Region.Op.UNION);
+            verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
+        }
+    }
+
+    private View createView(Rect bounds) {
+        final Rect rect = new Rect(bounds);
+        final View view = Mockito.mock(View.class);
+        doAnswer(invocation -> {
+            ((Rect) invocation.getArgument(0)).set(rect);
+            return null;
+        }).when(view).getBoundsOnScreen(any());
+
+        return view;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt
new file mode 100644
index 0000000..d4be881
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.user
+
+import android.os.UserManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.LayoutInflater
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.phone.ShadeController
+import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+class UserSwitcherActivityTest : SysuiTestCase() {
+    @Mock
+    private lateinit var activity: UserSwitcherActivity
+    @Mock
+    private lateinit var userSwitcherController: UserSwitcherController
+    @Mock
+    private lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock
+    private lateinit var layoutInflater: LayoutInflater
+    @Mock
+    private lateinit var falsingManager: FalsingManager
+    @Mock
+    private lateinit var userManager: UserManager
+    @Mock
+    private lateinit var shadeController: ShadeController
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        activity = UserSwitcherActivity(
+            userSwitcherController,
+            broadcastDispatcher,
+            layoutInflater,
+            falsingManager,
+            userManager,
+            shadeController
+        )
+    }
+
+    @Test
+    fun testMaxColumns() {
+        assertThat(activity.getMaxColumns(3)).isEqualTo(4)
+        assertThat(activity.getMaxColumns(4)).isEqualTo(4)
+        assertThat(activity.getMaxColumns(5)).isEqualTo(3)
+        assertThat(activity.getMaxColumns(6)).isEqualTo(3)
+        assertThat(activity.getMaxColumns(7)).isEqualTo(4)
+        assertThat(activity.getMaxColumns(9)).isEqualTo(5)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index b380553..ec619bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -36,6 +36,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.CaptioningManager;
 
 import androidx.test.filters.SmallTest;
 
@@ -88,6 +89,8 @@
     private PackageManager mPackageManager;
     @Mock
     private WakefulnessLifecycle mWakefullnessLifcycle;
+    @Mock
+    private CaptioningManager mCaptioningManager;
 
 
     @Before
@@ -109,7 +112,7 @@
         mVolumeController = new TestableVolumeDialogControllerImpl(mContext,
                 mBroadcastDispatcher, mRingerModeTracker, mThreadFactory, mAudioManager,
                 mNotificationManager, mVibrator, mIAudioService, mAccessibilityManager,
-                mPackageManager, mWakefullnessLifcycle, mCallback);
+                mPackageManager, mWakefullnessLifcycle, mCaptioningManager, mCallback);
         mVolumeController.setEnableDialogs(true, true);
     }
 
@@ -184,10 +187,11 @@
                 AccessibilityManager accessibilityManager,
                 PackageManager packageManager,
                 WakefulnessLifecycle wakefulnessLifecycle,
+                CaptioningManager captioningManager,
                 C callback) {
             super(context, broadcastDispatcher, ringerModeTracker, theadFactory, audioManager,
                     notificationManager, optionalVibrator, iAudioService, accessibilityManager,
-                    packageManager, wakefulnessLifecycle);
+                    packageManager, wakefulnessLifecycle, captioningManager);
             mCallbacks = callback;
 
             ArgumentCaptor<WakefulnessLifecycle.Observer> observerCaptor =
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 4bc4e6e..593b97e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -50,8 +50,10 @@
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
@@ -197,6 +199,11 @@
     private ArgumentCaptor<NotificationRemoveInterceptor> mRemoveInterceptorCaptor;
     @Captor
     private ArgumentCaptor<List<Bubble>> mBubbleListCaptor;
+    @Captor
+    private ArgumentCaptor<IntentFilter> mFilterArgumentCaptor;
+    @Captor
+    private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor;
+
 
     private BubblesManager mBubblesManager;
     // TODO(178618782): Move tests on the controller directly to the shell
@@ -1357,6 +1364,66 @@
         assertStackCollapsed();
     }
 
+    @Test
+    public void testRegisterUnregisterBroadcastListener() {
+        spyOn(mContext);
+        mBubbleController.updateBubble(mBubbleEntry);
+        verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
+                mFilterArgumentCaptor.capture());
+        assertThat(mFilterArgumentCaptor.getValue().getAction(0)).isEqualTo(
+                Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+        assertThat(mFilterArgumentCaptor.getValue().getAction(1)).isEqualTo(
+                Intent.ACTION_SCREEN_OFF);
+
+        mBubbleData.dismissBubbleWithKey(mBubbleEntry.getKey(), REASON_APP_CANCEL);
+        // TODO: not certain why this isn't called normally when tests are run, perhaps because
+        // it's after an animation in BSV. This calls BubbleController#removeFromWindowManagerMaybe
+        mBubbleController.onAllBubblesAnimatedOut();
+
+        verify(mContext).unregisterReceiver(eq(mBroadcastReceiverArgumentCaptor.getValue()));
+    }
+
+    @Test
+    public void testBroadcastReceiverCloseDialogs_notGestureNav() {
+        spyOn(mContext);
+        mBubbleController.updateBubble(mBubbleEntry);
+        mBubbleData.setExpanded(true);
+        verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
+                mFilterArgumentCaptor.capture());
+        Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
+
+        assertStackExpanded();
+    }
+
+    @Test
+    public void testBroadcastReceiverCloseDialogs_reasonGestureNav() {
+        spyOn(mContext);
+        mBubbleController.updateBubble(mBubbleEntry);
+        mBubbleData.setExpanded(true);
+
+        verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
+                mFilterArgumentCaptor.capture());
+        Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+        i.putExtra("reason", "gestureNav");
+        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
+        assertStackCollapsed();
+    }
+
+    @Test
+    public void testBroadcastReceiver_screenOff() {
+        spyOn(mContext);
+        mBubbleController.updateBubble(mBubbleEntry);
+        mBubbleData.setExpanded(true);
+
+        verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
+                mFilterArgumentCaptor.capture());
+
+        Intent i = new Intent(Intent.ACTION_SCREEN_OFF);
+        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
+        assertStackCollapsed();
+    }
+
     /** Creates a bubble using the userId and package. */
     private Bubble createBubble(int userId, String pkg) {
         final UserHandle userHandle = new UserHandle(userId);
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 75d8453..cc848bc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -46,6 +46,9 @@
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.LauncherApps;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.Handler;
@@ -179,6 +182,10 @@
     private ArgumentCaptor<NotifCollectionListener> mNotifListenerCaptor;
     @Captor
     private ArgumentCaptor<List<Bubble>> mBubbleListCaptor;
+    @Captor
+    private ArgumentCaptor<IntentFilter> mFilterArgumentCaptor;
+    @Captor
+    private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor;
 
     private BubblesManager mBubblesManager;
     private TestableBubbleController mBubbleController;
@@ -1176,6 +1183,67 @@
         assertStackCollapsed();
     }
 
+
+    @Test
+    public void testRegisterUnregisterBroadcastListener() {
+        spyOn(mContext);
+        mBubbleController.updateBubble(mBubbleEntry);
+        verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
+                mFilterArgumentCaptor.capture());
+        assertThat(mFilterArgumentCaptor.getValue().getAction(0)).isEqualTo(
+                Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+        assertThat(mFilterArgumentCaptor.getValue().getAction(1)).isEqualTo(
+                Intent.ACTION_SCREEN_OFF);
+
+        mBubbleData.dismissBubbleWithKey(mBubbleEntry.getKey(), REASON_APP_CANCEL);
+        // TODO: not certain why this isn't called normally when tests are run, perhaps because
+        // it's after an animation in BSV. This calls BubbleController#removeFromWindowManagerMaybe
+        mBubbleController.onAllBubblesAnimatedOut();
+
+        verify(mContext).unregisterReceiver(eq(mBroadcastReceiverArgumentCaptor.getValue()));
+    }
+
+    @Test
+    public void testBroadcastReceiverCloseDialogs_notGestureNav() {
+        spyOn(mContext);
+        mBubbleController.updateBubble(mBubbleEntry);
+        mBubbleData.setExpanded(true);
+        verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
+                mFilterArgumentCaptor.capture());
+        Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
+
+        assertStackExpanded();
+    }
+
+    @Test
+    public void testBroadcastReceiverCloseDialogs_reasonGestureNav() {
+        spyOn(mContext);
+        mBubbleController.updateBubble(mBubbleEntry);
+        mBubbleData.setExpanded(true);
+
+        verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
+                mFilterArgumentCaptor.capture());
+        Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+        i.putExtra("reason", "gestureNav");
+        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
+        assertStackCollapsed();
+    }
+
+    @Test
+    public void testBroadcastReceiver_screenOff() {
+        spyOn(mContext);
+        mBubbleController.updateBubble(mBubbleEntry);
+        mBubbleData.setExpanded(true);
+
+        verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
+                mFilterArgumentCaptor.capture());
+
+        Intent i = new Intent(Intent.ACTION_SCREEN_OFF);
+        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
+        assertStackCollapsed();
+    }
+
     /**
      * Sets the bubble metadata flags for this entry. These flags are normally set by
      * NotificationManagerService when the notification is sent, however, these tests do not
diff --git a/proto/src/camera.proto b/proto/src/camera.proto
index 2d62f32..4082118 100644
--- a/proto/src/camera.proto
+++ b/proto/src/camera.proto
@@ -64,7 +64,7 @@
     repeated int64 histogram_counts = 13;
 
     // The dynamic range profile of the stream
-    optional int32 dynamic_range_profile = 14;
+    optional int64 dynamic_range_profile = 14;
     // The stream use case
     optional int32 stream_use_case = 15;
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 62da981..0e99265 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1363,8 +1363,18 @@
      * </p>
      *
      * @param displayId The logical display id
-     * @param region the new magnified region, may be empty if
-     *               magnification is not enabled (e.g. scale is 1)
+     * @param region The magnification region.
+     *               If the config mode is
+     *               {@link MagnificationConfig#MAGNIFICATION_MODE_FULLSCREEN},
+     *               it is the region of the screen currently active for magnification.
+     *               the returned region will be empty if the magnification is not active
+     *               (e.g. scale is 1. And the magnification is active if magnification
+     *               gestures are enabled or if a service is running that can control
+     *               magnification.
+     *               If the config mode is
+     *               {@link MagnificationConfig#MAGNIFICATION_MODE_WINDOW},
+     *               it is the region of screen projected on the magnification window.
+     *               The region will be empty if magnification is not activated.
      * @param config The magnification config. That has magnification mode, the new scale and the
      *              new screen-relative center position
      */
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 59c1461..aba32ec 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -667,6 +667,10 @@
                 return null;
             }
 
+            // Don't need to add the embedded hierarchy windows into the accessibility windows list.
+            if (mHostEmbeddedMap.size() > 0 && isEmbeddedHierarchyWindowsLocked(windowId)) {
+                return null;
+            }
             final AccessibilityWindowInfo reportedWindow = AccessibilityWindowInfo.obtain();
 
             reportedWindow.setId(windowId);
@@ -699,6 +703,21 @@
             return reportedWindow;
         }
 
+        private boolean isEmbeddedHierarchyWindowsLocked(int windowId) {
+            final IBinder leashToken = mWindowIdMap.get(windowId);
+            if (leashToken == null) {
+                return false;
+            }
+
+            for (int i = 0; i < mHostEmbeddedMap.size(); i++) {
+                if (mHostEmbeddedMap.keyAt(i).equals(leashToken)) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
         private int getTypeForWindowManagerWindowType(int windowType) {
             switch (windowType) {
                 case WindowManager.LayoutParams.TYPE_APPLICATION:
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index fe97a46..a958209 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -50,7 +50,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
-import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.wm.WindowManagerInternal;
 
@@ -374,9 +373,8 @@
                     .setScale(getScale())
                     .setCenterX(getCenterX())
                     .setCenterY(getCenterY()).build();
-            mControllerCtx.getAms().notifyMagnificationChanged(mDisplayId,
-                    mMagnificationRegion,
-                    config);
+            mMagnificationInfoChangedCallback.onFullScreenMagnificationChanged(mDisplayId,
+                    mMagnificationRegion, config);
             if (mUnregisterPending && !isMagnifying()) {
                 unregister(mDeleteAfterUnregister);
             }
@@ -665,10 +663,10 @@
      * FullScreenMagnificationController Constructor
      */
     public FullScreenMagnificationController(@NonNull Context context,
-            @NonNull AccessibilityManagerService ams, @NonNull Object lock,
+            @NonNull AccessibilityTraceManager traceManager, @NonNull Object lock,
             @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback,
             @NonNull MagnificationScaleProvider scaleProvider) {
-        this(new ControllerContext(context, ams,
+        this(new ControllerContext(context, traceManager,
                 LocalServices.getService(WindowManagerInternal.class),
                 new Handler(context.getMainLooper()),
                 context.getResources().getInteger(R.integer.config_longAnimTime)), lock,
@@ -1521,7 +1519,6 @@
     @VisibleForTesting
     public static class ControllerContext {
         private final Context mContext;
-        private final AccessibilityManagerService mAms;
         private final AccessibilityTraceManager mTrace;
         private final WindowManagerInternal mWindowManager;
         private final Handler mHandler;
@@ -1531,13 +1528,12 @@
          * Constructor for ControllerContext.
          */
         public ControllerContext(@NonNull Context context,
-                @NonNull AccessibilityManagerService ams,
+                @NonNull AccessibilityTraceManager traceManager,
                 @NonNull WindowManagerInternal windowManager,
                 @NonNull Handler handler,
                 long animationDuration) {
             mContext = context;
-            mAms = ams;
-            mTrace = ams.getTraceManager();
+            mTrace = traceManager;
             mWindowManager = windowManager;
             mHandler = handler;
             mAnimationDuration = animationDuration;
@@ -1552,14 +1548,6 @@
         }
 
         /**
-         * @return AccessibilityManagerService
-         */
-        @NonNull
-        public AccessibilityManagerService getAms() {
-            return mAms;
-        }
-
-        /**
          * @return AccessibilityTraceManager
          */
         @NonNull
@@ -1632,5 +1620,17 @@
          *                           hidden.
          */
         void onImeWindowVisibilityChanged(boolean shown);
+
+        /**
+         * Called when the magnification spec changed.
+         *
+         * @param displayId The logical display id
+         * @param region    The region of the screen currently active for magnification.
+         *                  The returned region will be empty if the magnification is not active.
+         * @param config    The magnification config. That has magnification mode, the new scale and
+         *                  the new screen-relative center position
+         */
+        void onFullScreenMagnificationChanged(int displayId, @NonNull Region region,
+                @NonNull MagnificationConfig config);
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index c376bf8..09e82c7 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -405,6 +405,12 @@
         mAms.notifyMagnificationChanged(displayId, new Region(bounds), config);
     }
 
+    @Override
+    public void onFullScreenMagnificationChanged(int displayId, @NonNull Region region,
+            @NonNull MagnificationConfig config) {
+        mAms.notifyMagnificationChanged(displayId, region, config);
+    }
+
     private void disableFullScreenMagnificationIfNeeded(int displayId) {
         final FullScreenMagnificationController fullScreenMagnificationController =
                 getFullScreenMagnificationController();
@@ -590,7 +596,7 @@
         synchronized (mLock) {
             if (mFullScreenMagnificationController == null) {
                 mFullScreenMagnificationController = new FullScreenMagnificationController(mContext,
-                        mAms, mLock, this, mScaleProvider);
+                        mAms.getTraceManager(), mLock, this, mScaleProvider);
             }
         }
         return mFullScreenMagnificationController;
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index 2b7b977..8359374 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -26,6 +26,7 @@
 import static com.android.server.companion.PackageUtils.enforceUsesCompanionDeviceFeature;
 import static com.android.server.companion.PermissionsUtils.enforcePermissionsForAssociation;
 import static com.android.server.companion.RolesUtils.isRoleHolder;
+import static com.android.server.companion.Utils.prepareForIpc;
 
 import static java.util.Objects.requireNonNull;
 
@@ -47,7 +48,6 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.util.PackageUtils;
@@ -397,20 +397,4 @@
 
         return sameOemPackageCerts;
     }
-
-    /**
-     * Convert an instance of a "locally-defined" ResultReceiver to an instance of
-     * {@link android.os.ResultReceiver} itself, which the receiving process will be able to
-     * unmarshall.
-     */
-    private static <T extends ResultReceiver> ResultReceiver prepareForIpc(T resultReceiver) {
-        final Parcel parcel = Parcel.obtain();
-        resultReceiver.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-
-        final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel);
-        parcel.recycle();
-
-        return ipcFriendly;
-    }
 }
diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
index cb28254..c0181fe 100644
--- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
+++ b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
@@ -54,7 +54,7 @@
  * should use public {@link AssociationStore} interface.
  */
 @SuppressLint("LongLogTag")
-class AssociationStoreImpl implements AssociationStore {
+public class AssociationStoreImpl implements AssociationStore {
     private static final boolean DEBUG = false;
     private static final String TAG = "CompanionDevice_AssociationStore";
 
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
index c39b59a..ec4bfe0 100644
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -118,13 +118,14 @@
 
             serviceConnectors = CollectionUtils.map(companionServices, componentName ->
                             new CompanionDeviceServiceConnector(mContext, userId, componentName));
-            mBoundCompanionApplications.setValueForPackage(userId, packageName, serviceConnectors);
-        }
 
-        if (serviceConnectors.isEmpty()) {
-            Slog.e(TAG, "Can't find CompanionDeviceService implementer in package: "
-                    + packageName + ". Please check if they are correctly declared.");
-            return;
+            if (serviceConnectors.isEmpty()) {
+                Slog.e(TAG, "Can't find CompanionDeviceService implementer in package: "
+                        + packageName + ". Please check if they are correctly declared.");
+                return;
+            }
+
+            mBoundCompanionApplications.setValueForPackage(userId, packageName, serviceConnectors);
         }
 
         // The first connector in the list is always the primary connector: set a listener to it.
diff --git a/services/companion/java/com/android/server/companion/DataStoreUtils.java b/services/companion/java/com/android/server/companion/DataStoreUtils.java
index 8ac741a..73e68ec 100644
--- a/services/companion/java/com/android/server/companion/DataStoreUtils.java
+++ b/services/companion/java/com/android/server/companion/DataStoreUtils.java
@@ -33,15 +33,24 @@
 import java.io.File;
 import java.io.FileOutputStream;
 
-final class DataStoreUtils {
+/**
+ * Util class for CDM data stores
+ */
+public final class DataStoreUtils {
     private static final String TAG = "CompanionDevice_DataStoreUtils";
 
-    static boolean isStartOfTag(@NonNull XmlPullParser parser, @NonNull String tag)
+    /**
+     * Check if the parser pointer is at the start of the tag
+     */
+    public static boolean isStartOfTag(@NonNull XmlPullParser parser, @NonNull String tag)
             throws XmlPullParserException {
         return parser.getEventType() == START_TAG && tag.equals(parser.getName());
     }
 
-    static boolean isEndOfTag(@NonNull XmlPullParser parser, @NonNull String tag)
+    /**
+     * Check if the parser pointer is at the end of the tag
+     */
+    public static boolean isEndOfTag(@NonNull XmlPullParser parser, @NonNull String tag)
             throws XmlPullParserException {
         return parser.getEventType() == END_TAG && tag.equals(parser.getName());
     }
@@ -57,7 +66,7 @@
      * @return an AtomicFile for the user
      */
     @NonNull
-    static AtomicFile createStorageFileForUser(@UserIdInt int userId, String fileName) {
+    public static AtomicFile createStorageFileForUser(@UserIdInt int userId, String fileName) {
         return new AtomicFile(getBaseStorageFileForUser(userId, fileName));
     }
 
@@ -70,7 +79,7 @@
      * Writing to file could fail, for example, if the user has been recently removed and so was
      * their DE (/data/system_de/<user-id>/) directory.
      */
-    static void writeToFileSafely(
+    public static void writeToFileSafely(
             @NonNull AtomicFile file, @NonNull ThrowingConsumer<FileOutputStream> consumer) {
         try {
             file.write(consumer);
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java
index d0cc122..2487bef 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java
@@ -101,7 +101,7 @@
  * Since Android T the data is stored to "companion_device_manager.xml" file in
  * {@link Environment#getDataSystemDeDirectory(int) /data/system_de/}.
  *
- * See {@link #getBaseStorageFileForUser(int) getBaseStorageFileForUser()}
+ * See {@link #getStorageFileForUser(int)}
  *
  * <p>
  * Since Android T the data is stored using the v1 schema.
diff --git a/services/companion/java/com/android/server/companion/Utils.java b/services/companion/java/com/android/server/companion/Utils.java
new file mode 100644
index 0000000..b9f61ec
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/Utils.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion;
+
+import android.os.Parcel;
+import android.os.ResultReceiver;
+
+/**
+ * A miscellaneous util class for CDM
+ *
+ * @hide
+ */
+public final class Utils {
+
+    /**
+     * Convert an instance of a "locally-defined" ResultReceiver to an instance of
+     * {@link android.os.ResultReceiver} itself, which the receiving process will be able to
+     * unmarshall.
+     * @hide
+     */
+    public static <T extends ResultReceiver> ResultReceiver prepareForIpc(T resultReceiver) {
+        final Parcel parcel = Parcel.obtain();
+        resultReceiver.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        return ipcFriendly;
+    }
+
+    private Utils() {}
+}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
new file mode 100644
index 0000000..7e2b60a
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.datatransfer;
+
+import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
+import static android.app.PendingIntent.FLAG_ONE_SHOT;
+import static android.companion.CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME;
+import static android.content.ComponentName.createRelative;
+
+import static com.android.server.companion.Utils.prepareForIpc;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.UserIdInt;
+import android.app.PendingIntent;
+import android.companion.AssociationInfo;
+import android.companion.DeviceNotAssociatedException;
+import android.companion.SystemDataTransferRequest;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.ResultReceiver;
+import android.util.Slog;
+
+import com.android.server.companion.AssociationStoreImpl;
+import com.android.server.companion.CompanionDeviceManagerService;
+
+import java.util.List;
+
+/**
+ * This processor builds user consent intent for a given SystemDataTransferRequest and processes the
+ * request when the system is ready (a secure channel is established between the handhold and the
+ * companion device).
+ */
+public class SystemDataTransferProcessor {
+
+    private static final String LOG_TAG = SystemDataTransferProcessor.class.getSimpleName();
+
+    // Values from UI to SystemDataTransferProcessor via ResultReceiver
+    private static final int RESULT_CODE_SYSTEM_DATA_TRANSFER_ALLOWED = 0;
+    private static final int RESULT_CODE_SYSTEM_DATA_TRANSFER_DISALLOWED = 1;
+    private static final String EXTRA_SYSTEM_DATA_TRANSFER_REQUEST = "system_data_transfer_request";
+    private static final String EXTRA_SYSTEM_DATA_TRANSFER_RESULT_RECEIVER =
+            "system_data_transfer_result_receiver";
+    private static final ComponentName SYSTEM_DATA_TRANSFER_REQUEST_APPROVAL_ACTIVITY =
+            createRelative(COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
+                    ".CompanionDeviceDataTransferActivity");
+
+    private final Context mContext;
+    private final AssociationStoreImpl mAssociationStore;
+    private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
+
+    SystemDataTransferProcessor(CompanionDeviceManagerService service,
+            AssociationStoreImpl associationStore,
+            SystemDataTransferRequestStore systemDataTransferRequestStore) {
+        mContext = service.getContext();
+        mAssociationStore = associationStore;
+        mSystemDataTransferRequestStore = systemDataTransferRequestStore;
+    }
+
+    /**
+     * Build a PendingIntent of user consent dialog
+     */
+    public PendingIntent buildSystemDataTransferConfirmationIntent(@UserIdInt int userId,
+            SystemDataTransferRequest request) throws DeviceNotAssociatedException {
+        // The association must exist and either belong to the calling package,
+        // or the calling package must hold REQUEST_SYSTEM_DATA_TRANSFER permission.
+        int associationId = request.getAssociationId();
+        AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+        if (association == null) {
+            throw new DeviceNotAssociatedException(
+                    "Association id: " + associationId + " doesn't exist.");
+        } else {
+            // If the package is not the companion app which owns the association,
+            // it must hold REQUEST_SYSTEM_DATA_TRANSFER permission.
+            // TODO(b/204593788): uncomment the following with the API changes
+//            if (!association.getPackageName()
+//                    .equals(mContext.getPackageManager().getNameForUid(Binder.getCallingUid()))) {
+//                mContext.enforceCallingOrSelfPermission(
+//                        Manifest.permission.REQUEST_COMPANION_DEVICE_SYSTEM_DATA_TRANSFER,
+//                        "requestSystemDataTransfer requires REQUEST_SYSTEM_DATA_TRANSFER "
+//                                + "permission if the package doesn't own the association.");
+//            }
+
+            // Check if the request's data type has been requested before.
+            List<SystemDataTransferRequest> storedRequests =
+                    mSystemDataTransferRequestStore.readRequestsByAssociationId(userId,
+                            associationId);
+            for (SystemDataTransferRequest storedRequest : storedRequests) {
+                if (request.hasSameDataType(storedRequest)) {
+                    Slog.e(LOG_TAG, "The request has been sent before, you can not send "
+                            + "the same request type again.");
+                    return null;
+                }
+            }
+        }
+
+        Slog.i(LOG_TAG, "Creating PendingIntent for associationId: " + associationId + ", request: "
+                + request);
+
+        // Create an internal intent to launch the user consent dialog
+        final Bundle extras = new Bundle();
+        request.setUserId(userId);
+        extras.putParcelable(EXTRA_SYSTEM_DATA_TRANSFER_REQUEST, request);
+        extras.putParcelable(EXTRA_SYSTEM_DATA_TRANSFER_RESULT_RECEIVER,
+                prepareForIpc(mOnSystemDataTransferRequestConfirmationReceiver));
+
+        final Intent intent = new Intent();
+        intent.setComponent(SYSTEM_DATA_TRANSFER_REQUEST_APPROVAL_ACTIVITY);
+        intent.putExtras(extras);
+
+        // Create a PendingIntent
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return PendingIntent.getActivity(mContext, /*requestCode */ associationId, intent,
+                    FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private final ResultReceiver mOnSystemDataTransferRequestConfirmationReceiver =
+            new ResultReceiver(Handler.getMain()) {
+                @Override
+                protected void onReceiveResult(int resultCode, Bundle data) {
+                    Slog.d(LOG_TAG, "onReceiveResult() code=" + resultCode + ", "
+                            + "data=" + data);
+
+                    if (resultCode == RESULT_CODE_SYSTEM_DATA_TRANSFER_ALLOWED
+                            || resultCode == RESULT_CODE_SYSTEM_DATA_TRANSFER_DISALLOWED) {
+                        final SystemDataTransferRequest request =
+                                data.getParcelable(EXTRA_SYSTEM_DATA_TRANSFER_REQUEST);
+                        requireNonNull(request);
+
+                        request.setUserConsented(
+                                resultCode == RESULT_CODE_SYSTEM_DATA_TRANSFER_ALLOWED);
+                        Slog.i(LOG_TAG, "Recording request: " + request);
+                        mSystemDataTransferRequestStore.writeRequest(request.getUserId(), request);
+
+                        return;
+                    }
+
+                    Slog.e(LOG_TAG, "Unknown result code:" + resultCode);
+                }
+            };
+}
diff --git a/services/companion/java/com/android/server/companion/SystemDataTransferRequestDataStore.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
similarity index 63%
rename from services/companion/java/com/android/server/companion/SystemDataTransferRequestDataStore.java
rename to services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
index 38e5d16..1e29f56 100644
--- a/services/companion/java/com/android/server/companion/SystemDataTransferRequestDataStore.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.companion;
+package com.android.server.companion.datatransfer;
 
 import static com.android.internal.util.XmlUtils.readBooleanAttribute;
 import static com.android.internal.util.XmlUtils.readIntAttribute;
@@ -33,10 +33,12 @@
 import android.companion.SystemDataTransferRequest;
 import android.util.AtomicFile;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
 import android.util.Xml;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -49,15 +51,23 @@
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * The class is responsible for reading/writing SystemDataTransferRequest records from/to the disk.
- *
+ * <p>
  * The following snippet is a sample XML file stored in the disk.
  * <pre>{@code
  * <requests>
  *   <request
  *     association_id="1"
+ *     user_id="12"
+ *     is_user_consented="true"
  *     is_permission_sync_all_packages="false">
  *     <list name="permission_sync_packages">
  *       <string>com.sample.app1</string>
@@ -67,9 +77,9 @@
  * </requests>
  * }</pre>
  */
-public class SystemDataTransferRequestDataStore {
+public class SystemDataTransferRequestStore {
 
-    private static final String LOG_TAG = SystemDataTransferRequestDataStore.class.getSimpleName();
+    private static final String LOG_TAG = SystemDataTransferRequestStore.class.getSimpleName();
 
     private static final String FILE_NAME = "companion_device_system_data_transfer_requests.xml";
 
@@ -78,13 +88,78 @@
     private static final String XML_TAG_LIST = "list";
 
     private static final String XML_ATTR_ASSOCIATION_ID = "association_id";
+    private static final String XML_ATTR_USER_ID = "user_id";
+    private static final String XML_ATTR_IS_USER_CONSENTED = "is_user_consented";
     private static final String XML_ATTR_IS_PERMISSION_SYNC_ALL_PACKAGES =
             "is_permission_sync_all_packages";
     private static final String XML_ATTR_PERMISSION_SYNC_PACKAGES = "permission_sync_packages";
 
+    private static final int READ_FROM_DISK_TIMEOUT = 5; // in seconds
+
+    private final ExecutorService mExecutor;
     private final ConcurrentMap<Integer, AtomicFile> mUserIdToStorageFile =
             new ConcurrentHashMap<>();
 
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private final SparseArray<List<SystemDataTransferRequest>> mCachedPerUser = new SparseArray<>();
+
+    public SystemDataTransferRequestStore() {
+        mExecutor = Executors.newSingleThreadExecutor();
+    }
+
+    @NonNull
+    List<SystemDataTransferRequest> readRequestsByAssociationId(@UserIdInt int userId,
+            int associationId) {
+        List<SystemDataTransferRequest> cachedRequests;
+        synchronized (mLock) {
+            cachedRequests = readRequestsFromCache(userId);
+        }
+
+        List<SystemDataTransferRequest> requestsByAssociationId = new ArrayList<>();
+        for (SystemDataTransferRequest request : cachedRequests) {
+            if (request.getAssociationId() == associationId) {
+                requestsByAssociationId.add(request);
+            }
+        }
+        return requestsByAssociationId;
+    }
+
+    void writeRequest(@UserIdInt int userId, SystemDataTransferRequest request) {
+        List<SystemDataTransferRequest> cachedRequests;
+        synchronized (mLock) {
+            // Write to cache
+            cachedRequests = readRequestsFromCache(userId);
+            cachedRequests.add(request);
+            mCachedPerUser.set(userId, cachedRequests);
+        }
+        // Write to store
+        mExecutor.execute(() -> writeRequestsToStore(userId, cachedRequests));
+    }
+
+    @GuardedBy("mLock")
+    private List<SystemDataTransferRequest> readRequestsFromCache(@UserIdInt int userId) {
+        List<SystemDataTransferRequest> cachedRequests = mCachedPerUser.get(userId);
+        if (cachedRequests == null) {
+            Future<List<SystemDataTransferRequest>> future =
+                    mExecutor.submit(() -> readRequestsFromStore(userId));
+            try {
+                cachedRequests = future.get(READ_FROM_DISK_TIMEOUT, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                Slog.e(LOG_TAG, "Thread reading SystemDataTransferRequest from disk is "
+                        + "interrupted.");
+            } catch (ExecutionException e) {
+                Slog.e(LOG_TAG, "Error occurred while reading SystemDataTransferRequest "
+                        + "from disk.");
+            } catch (TimeoutException e) {
+                Slog.e(LOG_TAG, "Reading SystemDataTransferRequest from disk timed out.");
+            }
+            mCachedPerUser.set(userId, cachedRequests);
+        }
+        return cachedRequests;
+    }
+
     /**
      * Reads previously persisted data for the given user
      *
@@ -92,7 +167,7 @@
      * @return a list of SystemDataTransferRequest
      */
     @NonNull
-    List<SystemDataTransferRequest> readRequestsForUser(@UserIdInt int userId) {
+    private List<SystemDataTransferRequest> readRequestsFromStore(@UserIdInt int userId) {
         final AtomicFile file = getStorageFileForUser(userId);
         Slog.i(LOG_TAG, "Reading SystemDataTransferRequests for user " + userId + " from "
                 + "file=" + file.getBaseFile().getPath());
@@ -108,7 +183,7 @@
                 final TypedXmlPullParser parser = Xml.resolvePullParser(in);
                 XmlUtils.beginDocument(parser, XML_TAG_REQUESTS);
 
-                return readRequests(parser);
+                return readRequestsFromXml(parser);
             } catch (XmlPullParserException | IOException e) {
                 Slog.e(LOG_TAG, "Error while reading requests file", e);
                 return Collections.emptyList();
@@ -117,7 +192,7 @@
     }
 
     @NonNull
-    private List<SystemDataTransferRequest> readRequests(@NonNull TypedXmlPullParser parser)
+    private List<SystemDataTransferRequest> readRequestsFromXml(@NonNull TypedXmlPullParser parser)
             throws XmlPullParserException, IOException {
         if (!isStartOfTag(parser, XML_TAG_REQUESTS)) {
             throw new XmlPullParserException("The XML doesn't have start tag: " + XML_TAG_REQUESTS);
@@ -127,22 +202,26 @@
 
         while (true) {
             parser.nextTag();
-            if (isEndOfTag(parser, XML_TAG_REQUESTS)) break;
+            if (isEndOfTag(parser, XML_TAG_REQUESTS)) {
+                break;
+            }
             if (isStartOfTag(parser, XML_TAG_REQUEST)) {
-                requests.add(readRequest(parser));
+                requests.add(readRequestFromXml(parser));
             }
         }
 
         return requests;
     }
 
-    private SystemDataTransferRequest readRequest(@NonNull TypedXmlPullParser parser)
+    private SystemDataTransferRequest readRequestFromXml(@NonNull TypedXmlPullParser parser)
             throws XmlPullParserException, IOException {
         if (!isStartOfTag(parser, XML_TAG_REQUEST)) {
             throw new XmlPullParserException("XML doesn't have start tag: " + XML_TAG_REQUEST);
         }
 
         final int associationId = readIntAttribute(parser, XML_ATTR_ASSOCIATION_ID);
+        final int userId = readIntAttribute(parser, XML_ATTR_USER_ID);
+        final boolean isUserConsented = readBooleanAttribute(parser, XML_ATTR_IS_USER_CONSENTED);
         final boolean isPermissionSyncAllPackages = readBooleanAttribute(parser,
                 XML_ATTR_IS_PERMISSION_SYNC_ALL_PACKAGES);
         parser.nextTag();
@@ -153,8 +232,13 @@
                     new String[1]);
         }
 
-        return new SystemDataTransferRequest(associationId, isPermissionSyncAllPackages,
-                permissionSyncPackages);
+        SystemDataTransferRequest request =
+                new SystemDataTransferRequest(associationId, isPermissionSyncAllPackages,
+                        permissionSyncPackages);
+        request.setUserId(userId);
+        request.setUserConsented(isUserConsented);
+
+        return request;
     }
 
     /**
@@ -163,7 +247,7 @@
      * @param userId   Android UserID
      * @param requests a list of user's SystemDataTransferRequest.
      */
-    void writeRequestsForUser(@UserIdInt int userId,
+    void writeRequestsToStore(@UserIdInt int userId,
             @NonNull List<SystemDataTransferRequest> requests) {
         final AtomicFile file = getStorageFileForUser(userId);
         Slog.i(LOG_TAG, "Writing SystemDataTransferRequests for user " + userId + " to file="
@@ -177,28 +261,30 @@
                 serializer.setFeature(
                         "http://xmlpull.org/v1/doc/features.html#indent-output", true);
                 serializer.startDocument(null, true);
-                writeRequests(serializer, requests);
+                writeRequestsToXml(serializer, requests);
                 serializer.endDocument();
             });
         }
     }
 
-    private void writeRequests(@NonNull TypedXmlSerializer serializer,
+    private void writeRequestsToXml(@NonNull TypedXmlSerializer serializer,
             @Nullable Collection<SystemDataTransferRequest> requests) throws IOException {
         serializer.startTag(null, XML_TAG_REQUESTS);
 
         for (SystemDataTransferRequest request : requests) {
-            writeRequest(serializer, request);
+            writeRequestToXml(serializer, request);
         }
 
         serializer.endTag(null, XML_TAG_REQUESTS);
     }
 
-    private void writeRequest(@NonNull TypedXmlSerializer serializer,
+    private void writeRequestToXml(@NonNull TypedXmlSerializer serializer,
             @NonNull SystemDataTransferRequest request) throws IOException {
         serializer.startTag(null, XML_TAG_REQUEST);
 
         writeIntAttribute(serializer, XML_ATTR_ASSOCIATION_ID, request.getAssociationId());
+        writeIntAttribute(serializer, XML_ATTR_USER_ID, request.getUserId());
+        writeBooleanAttribute(serializer, XML_ATTR_IS_USER_CONSENTED, request.isUserConsented());
         writeBooleanAttribute(serializer, XML_ATTR_IS_PERMISSION_SYNC_ALL_PACKAGES,
                 request.isPermissionSyncAllPackages());
         try {
@@ -215,11 +301,12 @@
     /**
      * Creates and caches {@link AtomicFile} object that represents the back-up file for the given
      * user.
-     *
+     * <p>
      * IMPORTANT: the method will ALWAYS return the same {@link AtomicFile} object, which makes it
      * possible to synchronize reads and writes to the file using the returned object.
      */
-    private @NonNull AtomicFile getStorageFileForUser(@UserIdInt int userId) {
+    @NonNull
+    private AtomicFile getStorageFileForUser(@UserIdInt int userId) {
         return mUserIdToStorageFile.computeIfAbsent(userId,
                 u -> createStorageFileForUser(userId, FILE_NAME));
     }
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 6986d3b..1f8ef82 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -18,14 +18,22 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ModuleInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.os.Build;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.ServiceManager;
 import android.os.ShellCallback;
 import android.os.ShellCommand;
 import android.os.SystemProperties;
@@ -42,6 +50,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Executors;
 import java.util.stream.Collectors;
 
 /**
@@ -49,6 +58,7 @@
  */
 public class BinaryTransparencyService extends SystemService {
     private static final String TAG = "TransparencyService";
+    private static final String EXTRA_SERVICE = "service";
 
     @VisibleForTesting
     static final String VBMETA_DIGEST_UNINITIALIZED = "vbmeta-digest-uninitialized";
@@ -365,10 +375,80 @@
 
         // we are only interested in doing things at PHASE_BOOT_COMPLETED
         if (phase == PHASE_BOOT_COMPLETED) {
-            // due to potentially long computation that holds up boot time, apex sha computations
-            // are deferred to first call
             Slog.i(TAG, "Boot completed. Getting VBMeta Digest.");
             getVBMetaDigestInformation();
+
+            // due to potentially long computation that holds up boot time, computations for
+            // SHA256 digests of APEX and Module packages are scheduled here,
+            // but only executed when device is idle.
+            Slog.i(TAG, "Scheduling APEX and Module measurements to be updated.");
+            UpdateMeasurementsJobService.scheduleBinaryMeasurements(mContext,
+                    BinaryTransparencyService.this);
+        }
+    }
+
+    /**
+     * JobService to update binary measurements and update internal cache.
+     */
+    public static class UpdateMeasurementsJobService extends JobService {
+        private static final int COMPUTE_APEX_MODULE_SHA256_JOB_ID =
+                BinaryTransparencyService.UpdateMeasurementsJobService.class.hashCode();
+
+        @Override
+        public boolean onStartJob(JobParameters params) {
+            Slog.d(TAG, "Job to update binary measurements started.");
+            if (params.getJobId() != COMPUTE_APEX_MODULE_SHA256_JOB_ID) {
+                return false;
+            }
+
+            // we'll still update the measurements via threads to be mindful of low-end devices
+            // where this operation might take longer than expected, and so that we don't block
+            // system_server's main thread.
+            Executors.defaultThreadFactory().newThread(() -> {
+                // since we can't call updateBinaryMeasurements() directly, calling
+                // getApexInfo() achieves the same effect, and we simply discard the return
+                // value
+
+                IBinder b = ServiceManager.getService(Context.BINARY_TRANSPARENCY_SERVICE);
+                IBinaryTransparencyService iBtsService =
+                        IBinaryTransparencyService.Stub.asInterface(b);
+                try {
+                    iBtsService.getApexInfo();
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Updating binary measurements was interrupted.", e);
+                    return;
+                }
+                jobFinished(params, false);
+            }).start();
+
+            return true;
+        }
+
+        @Override
+        public boolean onStopJob(JobParameters params) {
+            return false;
+        }
+
+        @SuppressLint("DefaultLocale")
+        static void scheduleBinaryMeasurements(Context context, BinaryTransparencyService service) {
+            Slog.i(TAG, "Scheduling APEX & Module SHA256 digest computation job");
+            final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+            if (jobScheduler == null) {
+                Slog.e(TAG, "Failed to obtain an instance of JobScheduler.");
+                return;
+            }
+
+            final JobInfo jobInfo = new JobInfo.Builder(COMPUTE_APEX_MODULE_SHA256_JOB_ID,
+                    new ComponentName(context, UpdateMeasurementsJobService.class))
+                    .setRequiresDeviceIdle(true)
+                    .build();
+            if (jobScheduler.schedule(jobInfo) != JobScheduler.RESULT_SUCCESS) {
+                Slog.e(TAG, "Failed to schedule job to update binary measurements.");
+                return;
+            }
+            Slog.d(TAG, String.format(
+                    "Job %d to update binary measurements scheduled successfully.",
+                    COMPUTE_APEX_MODULE_SHA256_JOB_ID));
         }
     }
 
@@ -380,7 +460,7 @@
 
     @NonNull
     private List<PackageInfo> getInstalledApexs() {
-        List<PackageInfo> results = new ArrayList<PackageInfo>();
+        List<PackageInfo> results = new ArrayList<>();
         PackageManager pm = mContext.getPackageManager();
         if (pm == null) {
             Slog.e(TAG, "Error obtaining an instance of PackageManager.");
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8c04c1e..1eb23ea 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8632,8 +8632,8 @@
             sb.append("Foreground: ")
                     .append(process.isInterestingToUserLocked() ? "Yes" : "No")
                     .append("\n");
-            if (process.getStartTime() > 0) {
-                long runtimeMillis = SystemClock.elapsedRealtime() - process.getStartTime();
+            if (process.getStartUptime() > 0) {
+                long runtimeMillis = SystemClock.uptimeMillis() - process.getStartUptime();
                 sb.append("Process-Runtime: ").append(runtimeMillis).append("\n");
             }
         }
@@ -9176,6 +9176,7 @@
         boolean dumpVisibleStacksOnly = false;
         boolean dumpFocusedStackOnly = false;
         String dumpPackage = null;
+        int dumpUserId = UserHandle.USER_ALL;
 
         int opti = 0;
         while (opti < args.length) {
@@ -9207,6 +9208,17 @@
                 dumpCheckinFormat = true;
             } else if ("--normal-priority".equals(opt)) {
                 dumpNormalPriority = true;
+            } else if ("--user".equals(opt)) {
+                if (opti < args.length) {
+                    dumpUserId = UserHandle.parseUserArg(args[opti]);
+                    if (dumpUserId == UserHandle.USER_CURRENT) {
+                        dumpUserId = mUserController.getCurrentUserId();
+                    }
+                    opti++;
+                } else {
+                    pw.println("Error: --user option requires user id argument");
+                    return;
+                }
             } else if ("-h".equals(opt)) {
                 ActivityManagerShellCommand.dumpHelp(pw, true);
                 return;
@@ -9397,29 +9409,17 @@
             } else if ("service".equals(cmd)) {
                 String[] newArgs;
                 String name;
-                int[] users = null;
                 if (opti >= args.length) {
                     name = null;
                     newArgs = EMPTY_STRING_ARRAY;
                 } else {
                     name = args[opti];
                     opti++;
-                    if ("--user".equals(name) && opti < args.length) {
-                        int userId = UserHandle.parseUserArg(args[opti]);
-                        opti++;
-                        if (userId != UserHandle.USER_ALL) {
-                            if (userId == UserHandle.USER_CURRENT) {
-                                userId = getCurrentUser().id;
-                            }
-                            users = new int[] { userId };
-                        }
-                        name = args[opti];
-                        opti++;
-                    }
                     newArgs = new String[args.length - opti];
                     if (args.length > 2) System.arraycopy(args, opti, newArgs, 0,
                             args.length - opti);
                 }
+                int[] users = dumpUserId == UserHandle.USER_ALL ? null : new int[] { dumpUserId };
                 if (!mServices.dumpService(fd, pw, name, users, newArgs, 0, dumpAll)) {
                     pw.println("No services match: " + name);
                     pw.println("Use -h for help.");
@@ -9480,7 +9480,7 @@
             } else {
                 // Dumping a single activity?
                 if (!mAtmInternal.dumpActivity(fd, pw, cmd, args, opti, dumpAll,
-                        dumpVisibleStacksOnly, dumpFocusedStackOnly)) {
+                        dumpVisibleStacksOnly, dumpFocusedStackOnly, dumpUserId)) {
                     ActivityManagerShellCommand shell = new ActivityManagerShellCommand(this, true);
                     int res = shell.exec(this, null, fd, null, args, null,
                             new ResultReceiver(null));
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index b813bc4..0edbea0 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -90,6 +90,7 @@
 import com.android.internal.compat.CompatibilityOverrideConfig;
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
@@ -268,16 +269,34 @@
                     break;
                 }
                 case SET_GAME_STATE: {
-                    if (mPowerManagerInternal == null) {
-                        final Bundle data = msg.getData();
-                        Slog.d(TAG, "Error setting loading mode for package "
-                                + data.getString(PACKAGE_NAME_MSG_KEY)
-                                + " and userId " + data.getInt(USER_ID_MSG_KEY));
-                        break;
-                    }
                     final GameState gameState = (GameState) msg.obj;
                     final boolean isLoading = gameState.isLoading();
-                    mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, isLoading);
+                    final Bundle data = msg.getData();
+                    final String packageName = data.getString(PACKAGE_NAME_MSG_KEY);
+                    final int userId = data.getInt(USER_ID_MSG_KEY);
+
+                    // Restrict to games only. Requires performance mode to be enabled.
+                    final boolean boostEnabled =
+                            getGameMode(packageName, userId) == GameManager.GAME_MODE_PERFORMANCE;
+                    int uid;
+                    try {
+                        uid = mPackageManager.getPackageUidAsUser(packageName, userId);
+                    } catch (NameNotFoundException e) {
+                        Slog.v(TAG, "Failed to get package metadata");
+                        uid = -1;
+                    }
+                    FrameworkStatsLog.write(FrameworkStatsLog.GAME_STATE_CHANGED, packageName, uid,
+                            boostEnabled, gameStateModeToStatsdGameState(gameState.getMode()),
+                            isLoading, gameState.getLabel(), gameState.getQuality());
+
+                    if (boostEnabled) {
+                        if (mPowerManagerInternal == null) {
+                            Slog.d(TAG, "Error setting loading mode for package " + packageName
+                                    + " and userId " + userId);
+                            break;
+                        }
+                        mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, isLoading);
+                    }
                     break;
                 }
             }
@@ -387,12 +406,6 @@
             // Restrict to games only.
             return;
         }
-
-        if (getGameMode(packageName, userId) != GameManager.GAME_MODE_PERFORMANCE) {
-            // Requires performance mode to be enabled.
-            return;
-        }
-
         final Message msg = mHandler.obtainMessage(SET_GAME_STATE);
         final Bundle data = new Bundle();
         data.putString(PACKAGE_NAME_MSG_KEY, packageName);
@@ -1543,6 +1556,22 @@
         return out.toString();
     }
 
+    private static int gameStateModeToStatsdGameState(int mode) {
+        switch (mode) {
+            case GameState.MODE_NONE:
+                return FrameworkStatsLog.GAME_STATE_CHANGED__STATE__MODE_NONE;
+            case GameState.MODE_GAMEPLAY_INTERRUPTIBLE:
+                return FrameworkStatsLog.GAME_STATE_CHANGED__STATE__MODE_GAMEPLAY_INTERRUPTIBLE;
+            case GameState.MODE_GAMEPLAY_UNINTERRUPTIBLE:
+                return FrameworkStatsLog.GAME_STATE_CHANGED__STATE__MODE_GAMEPLAY_UNINTERRUPTIBLE;
+            case GameState.MODE_CONTENT:
+                return FrameworkStatsLog.GAME_STATE_CHANGED__STATE__MODE_CONTENT;
+            case GameState.MODE_UNKNOWN:
+            default:
+                return FrameworkStatsLog.GAME_STATE_CHANGED__STATE__MODE_UNKNOWN;
+        }
+    }
+
     private static ServiceThread createServiceThread() {
         ServiceThread handlerThread = new ServiceThread(TAG,
                 Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 3491cd5..49a935e 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -479,6 +479,8 @@
         }
         if (profile == BluetoothProfile.A2DP) {
             mA2dp = (BluetoothA2dp) proxy;
+        } else if (profile == BluetoothProfile.HEARING_AID) {
+            mHearingAid = (BluetoothHearingAid) proxy;
         } else if (profile == BluetoothProfile.LE_AUDIO) {
             mLeAudio = (BluetoothLeAudio) proxy;
         }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index ea054a5..682f0df 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -2020,7 +2020,8 @@
                             .setCategory(Notification.CATEGORY_SYSTEM)
                             .setVisibility(Notification.VISIBILITY_PUBLIC)
                             .setOngoing(true)
-                            .setColor(mContext.getColor(R.color.system_notification_accent_color));
+                            .setColor(mContext.getColor(
+                                    android.R.color.system_notification_accent_color));
             notificationManager.notify(TAG, SystemMessage.NOTE_VPN_DISCONNECTED, builder.build());
         } finally {
             Binder.restoreCallingIdentity(token);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 7550d7e..9d92a9e4 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2463,7 +2463,7 @@
             pw.println("  mMinimumBrightnessCurve=" + mMinimumBrightnessCurve);
 
             if (mUserPreferredMode != null) {
-                pw.println(mUserPreferredMode);
+                pw.println(" mUserPreferredMode=" + mUserPreferredMode);
             }
 
             pw.println();
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 9067f2e..accdd56 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -63,6 +63,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.RingBuffer;
 import com.android.server.LocalServices;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.display.RampAnimator.DualRampAnimator;
@@ -155,6 +156,8 @@
     private static final int REPORTED_TO_POLICY_SCREEN_ON = 2;
     private static final int REPORTED_TO_POLICY_SCREEN_TURNING_OFF = 3;
 
+    private static final int RINGBUFFER_MAX = 100;
+
     private final String TAG;
 
     private final Object mLock = new Object();
@@ -212,6 +215,9 @@
 
     private final float mScreenBrightnessDefault;
 
+    // Previously logged screen brightness. Used for autobrightness event dumpsys.
+    private float mPreviousScreenBrightness = Float.NaN;
+
     // The minimum allowed brightness while in VR.
     private final float mScreenBrightnessForVrRangeMinimum;
 
@@ -387,6 +393,9 @@
 
     private final Runnable mOnBrightnessChangeRunnable;
 
+    // Used for keeping record in dumpsys for when and to which brightness auto adaptions were made.
+    private RingBuffer<AutobrightnessEvent> mAutobrightnessEventRingBuffer;
+
     // A record of state for skipping brightness ramps.
     private int mSkipRampState = RAMP_STATE_SKIP_NONE;
 
@@ -988,6 +997,9 @@
                     mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper,
                     mDisplayDeviceConfig.getAmbientHorizonShort(),
                     mDisplayDeviceConfig.getAmbientHorizonLong());
+
+            mAutobrightnessEventRingBuffer =
+                    new RingBuffer<>(AutobrightnessEvent.class, RINGBUFFER_MAX);
         } else {
             mUseSoftwareAutoBrightnessConfig = false;
         }
@@ -1558,6 +1570,15 @@
             Slog.v(TAG, "Brightness [" + brightnessState + "] manual adjustment.");
         }
 
+        // Add any automatic changes to autobrightness ringbuffer for dumpsys.
+        if (mBrightnessReason.reason == BrightnessReason.REASON_AUTOMATIC
+                && !BrightnessSynchronizer.floatEquals(
+                        mPreviousScreenBrightness, brightnessState)) {
+            mPreviousScreenBrightness = brightnessState;
+            mAutobrightnessEventRingBuffer.append(new AutobrightnessEvent(
+                    System.currentTimeMillis(), brightnessState));
+        }
+
         // Update display white-balance.
         if (mDisplayWhiteBalanceController != null) {
             if (state == Display.STATE_ON && mDisplayWhiteBalanceSettings.isEnabled()) {
@@ -2491,6 +2512,7 @@
 
         if (mAutomaticBrightnessController != null) {
             mAutomaticBrightnessController.dump(pw);
+            dumpAutobrightnessEvents(pw);
         }
 
         if (mHbmController != null) {
@@ -2547,6 +2569,20 @@
         }
     }
 
+    private void dumpAutobrightnessEvents(PrintWriter pw) {
+        int size = mAutobrightnessEventRingBuffer.size();
+        if (size < 1) {
+            pw.println("No Automatic Brightness Adjustments");
+            return;
+        }
+
+        pw.println("Automatic Brightness Adjustments Last " + size + " Events: ");
+        AutobrightnessEvent[] eventArray = mAutobrightnessEventRingBuffer.toArray();
+        for (int i = 0; i < mAutobrightnessEventRingBuffer.size(); i++) {
+            pw.println("  " + eventArray[i].toString());
+        }
+    }
+
     private static float clampAbsoluteBrightness(float value) {
         return MathUtils.constrain(value, PowerManager.BRIGHTNESS_MIN,
                 PowerManager.BRIGHTNESS_MAX);
@@ -2615,6 +2651,21 @@
         }
     }
 
+    private static class AutobrightnessEvent {
+        final long mTime;
+        final float mBrightness;
+
+        AutobrightnessEvent(long time, float brightness) {
+            mTime = time;
+            mBrightness = brightness;
+        }
+
+        @Override
+        public String toString() {
+            return TimeUtils.formatForLogging(mTime) + " - Brightness: " + mBrightness;
+        }
+    }
+
     private final class DisplayControllerHandler extends Handler {
         public DisplayControllerHandler(Looper looper) {
             super(looper, null, true /*async*/);
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 2e80efb..7a0cf4b 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -884,20 +884,26 @@
         public void setUserPreferredDisplayModeLocked(Display.Mode mode) {
             final int oldModeId = getPreferredModeId();
             mUserPreferredMode = mode;
-            if (mode != null && (mode.isRefreshRateSet() ^ mode.isResolutionSet())) {
-                mUserPreferredMode = findMode(mode.getPhysicalWidth(),
+            if (mode != null && (mode.isRefreshRateSet() || mode.isResolutionSet())) {
+                Display.Mode matchingSupportedMode;
+                matchingSupportedMode = findMode(mode.getPhysicalWidth(),
                         mode.getPhysicalHeight(), mode.getRefreshRate());
+                if (matchingSupportedMode != null) {
+                    mUserPreferredMode = matchingSupportedMode;
+                }
             }
-            mUserPreferredModeId = findUserPreferredModeIdLocked(mode);
 
-            if (oldModeId != getPreferredModeId()) {
-                updateDeviceInfoLocked();
+            mUserPreferredModeId = findUserPreferredModeIdLocked(mUserPreferredMode);
+
+            if (oldModeId == getPreferredModeId()) {
+                return;
             }
+            updateDeviceInfoLocked();
 
             if (!mSurfaceControlProxy.getBootDisplayModeSupport()) {
                 return;
             }
-            if (mUserPreferredMode == null) {
+            if (mUserPreferredModeId == INVALID_MODE_ID) {
                 mSurfaceControlProxy.clearBootDisplayMode(getDisplayTokenLocked());
             } else {
                 int preferredSfDisplayModeId = findSfDisplayModeIdLocked(
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index 76754d3..4a1a950 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -104,7 +104,7 @@
             pw.println("  mCurrentDream:");
             pw.println("    mToken=" + mCurrentDream.mToken);
             pw.println("    mName=" + mCurrentDream.mName);
-            pw.println("    mIsTest=" + mCurrentDream.mIsTest);
+            pw.println("    mIsPreviewMode=" + mCurrentDream.mIsPreviewMode);
             pw.println("    mCanDoze=" + mCurrentDream.mCanDoze);
             pw.println("    mUserId=" + mCurrentDream.mUserId);
             pw.println("    mBound=" + mCurrentDream.mBound);
@@ -117,7 +117,7 @@
     }
 
     public void startDream(Binder token, ComponentName name,
-            boolean isTest, boolean canDoze, int userId, PowerManager.WakeLock wakeLock,
+            boolean isPreviewMode, boolean canDoze, int userId, PowerManager.WakeLock wakeLock,
             ComponentName overlayComponentName) {
         stopDream(true /*immediate*/, "starting new dream");
 
@@ -127,10 +127,10 @@
             mContext.sendBroadcastAsUser(mCloseNotificationShadeIntent, UserHandle.ALL);
 
             Slog.i(TAG, "Starting dream: name=" + name
-                    + ", isTest=" + isTest + ", canDoze=" + canDoze
+                    + ", isPreviewMode=" + isPreviewMode + ", canDoze=" + canDoze
                     + ", userId=" + userId);
 
-            mCurrentDream = new DreamRecord(token, name, isTest, canDoze, userId, wakeLock);
+            mCurrentDream = new DreamRecord(token, name, isPreviewMode, canDoze, userId, wakeLock);
 
             mDreamStartTime = SystemClock.elapsedRealtime();
             MetricsLogger.visible(mContext,
@@ -140,6 +140,7 @@
             intent.setComponent(name);
             intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
             intent.putExtra(DreamService.EXTRA_DREAM_OVERLAY_COMPONENT, overlayComponentName);
+            intent.putExtra(DreamService.EXTRA_IS_PREVIEW, isPreviewMode);
             try {
                 if (!mContext.bindServiceAsUser(intent, mCurrentDream,
                         Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
@@ -190,7 +191,8 @@
             final DreamRecord oldDream = mCurrentDream;
             mCurrentDream = null;
             Slog.i(TAG, "Stopping dream: name=" + oldDream.mName
-                    + ", isTest=" + oldDream.mIsTest + ", canDoze=" + oldDream.mCanDoze
+                    + ", isPreviewMode=" + oldDream.mIsPreviewMode
+                    + ", canDoze=" + oldDream.mCanDoze
                     + ", userId=" + oldDream.mUserId
                     + ", reason='" + reason + "'"
                     + (mSavedStopReason == null ? "" : "(from '" + mSavedStopReason + "')"));
@@ -247,7 +249,7 @@
 
         mCurrentDream.mService = service;
 
-        if (!mCurrentDream.mIsTest) {
+        if (!mCurrentDream.mIsPreviewMode) {
             mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL);
             mCurrentDream.mSentStartBroadcast = true;
         }
@@ -263,7 +265,7 @@
     private final class DreamRecord implements DeathRecipient, ServiceConnection {
         public final Binder mToken;
         public final ComponentName mName;
-        public final boolean mIsTest;
+        public final boolean mIsPreviewMode;
         public final boolean mCanDoze;
         public final int mUserId;
 
@@ -275,11 +277,11 @@
 
         public boolean mWakingGently;
 
-        public DreamRecord(Binder token, ComponentName name,
-                boolean isTest, boolean canDoze, int userId, PowerManager.WakeLock wakeLock) {
+        DreamRecord(Binder token, ComponentName name, boolean isPreviewMode,
+                boolean canDoze, int userId, PowerManager.WakeLock wakeLock) {
             mToken = token;
             mName = name;
-            mIsTest = isTest;
+            mIsPreviewMode = isPreviewMode;
             mCanDoze = canDoze;
             mUserId  = userId;
             mWakeLock = wakeLock;
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index f0a6af3..22d32a6 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -87,7 +87,7 @@
     private Binder mCurrentDreamToken;
     private ComponentName mCurrentDreamName;
     private int mCurrentDreamUserId;
-    private boolean mCurrentDreamIsTest;
+    private boolean mCurrentDreamIsPreview;
     private boolean mCurrentDreamCanDoze;
     private boolean mCurrentDreamIsDozing;
     private boolean mCurrentDreamIsWaking;
@@ -169,7 +169,7 @@
         pw.println("mCurrentDreamToken=" + mCurrentDreamToken);
         pw.println("mCurrentDreamName=" + mCurrentDreamName);
         pw.println("mCurrentDreamUserId=" + mCurrentDreamUserId);
-        pw.println("mCurrentDreamIsTest=" + mCurrentDreamIsTest);
+        pw.println("mCurrentDreamIsPreview=" + mCurrentDreamIsPreview);
         pw.println("mCurrentDreamCanDoze=" + mCurrentDreamCanDoze);
         pw.println("mCurrentDreamIsDozing=" + mCurrentDreamIsDozing);
         pw.println("mCurrentDreamIsWaking=" + mCurrentDreamIsWaking);
@@ -190,7 +190,7 @@
 
     private boolean isDreamingInternal() {
         synchronized (mLock) {
-            return mCurrentDreamToken != null && !mCurrentDreamIsTest
+            return mCurrentDreamToken != null && !mCurrentDreamIsPreview
                     && !mCurrentDreamIsWaking;
         }
     }
@@ -235,7 +235,7 @@
 
     private void testDreamInternal(ComponentName dream, int userId) {
         synchronized (mLock) {
-            startDreamLocked(dream, true /*isTest*/, false /*canDoze*/, userId);
+            startDreamLocked(dream, true /*isPreviewMode*/, false /*canDoze*/, userId);
         }
     }
 
@@ -244,7 +244,7 @@
         final ComponentName dream = chooseDreamForUser(doze, userId);
         if (dream != null) {
             synchronized (mLock) {
-                startDreamLocked(dream, false /*isTest*/, doze, userId);
+                startDreamLocked(dream, false /*isPreviewMode*/, doze, userId);
             }
         }
     }
@@ -395,10 +395,10 @@
     }
 
     private void startDreamLocked(final ComponentName name,
-            final boolean isTest, final boolean canDoze, final int userId) {
+            final boolean isPreviewMode, final boolean canDoze, final int userId) {
         if (!mCurrentDreamIsWaking
                 && Objects.equals(mCurrentDreamName, name)
-                && mCurrentDreamIsTest == isTest
+                && mCurrentDreamIsPreview == isPreviewMode
                 && mCurrentDreamCanDoze == canDoze
                 && mCurrentDreamUserId == userId) {
             Slog.i(TAG, "Already in target dream.");
@@ -412,7 +412,7 @@
         final Binder newToken = new Binder();
         mCurrentDreamToken = newToken;
         mCurrentDreamName = name;
-        mCurrentDreamIsTest = isTest;
+        mCurrentDreamIsPreview = isPreviewMode;
         mCurrentDreamCanDoze = canDoze;
         mCurrentDreamUserId = userId;
 
@@ -424,7 +424,7 @@
                 .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "startDream");
         mHandler.post(wakeLock.wrap(() -> {
             mAtmInternal.notifyDreamStateChanged(true);
-            mController.startDream(newToken, name, isTest, canDoze, userId, wakeLock,
+            mController.startDream(newToken, name, isPreviewMode, canDoze, userId, wakeLock,
                     mDreamOverlayServiceName);
         }));
     }
@@ -457,7 +457,7 @@
         }
         mCurrentDreamToken = null;
         mCurrentDreamName = null;
-        mCurrentDreamIsTest = false;
+        mCurrentDreamIsPreview = false;
         mCurrentDreamCanDoze = false;
         mCurrentDreamUserId = 0;
         mCurrentDreamIsWaking = false;
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index c15242a..140a28f 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -144,6 +144,7 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
+import java.util.OptionalInt;
 
 /*
  * Wraps the C++ InputManager and provides its callbacks.
@@ -2915,48 +2916,17 @@
 
     // Native callback
     @SuppressWarnings("unused")
-    private void notifyWindowUnresponsive(IBinder token, String reason) {
-        int gestureMonitorPid = -1;
-        synchronized (mInputMonitors) {
-            final GestureMonitorSpyWindow gestureMonitor = mInputMonitors.get(token);
-            if (gestureMonitor != null) {
-                gestureMonitorPid = gestureMonitor.mWindowHandle.ownerPid;
-            }
-        }
-        if (gestureMonitorPid != -1) {
-            mWindowManagerCallbacks.notifyGestureMonitorUnresponsive(gestureMonitorPid, reason);
-            return;
-        }
-        mWindowManagerCallbacks.notifyWindowUnresponsive(token, reason);
+    private void notifyWindowUnresponsive(IBinder token, int pid, boolean isPidValid,
+            String reason) {
+        mWindowManagerCallbacks.notifyWindowUnresponsive(token,
+                isPidValid ? OptionalInt.of(pid) : OptionalInt.empty(), reason);
     }
 
     // Native callback
     @SuppressWarnings("unused")
-    private void notifyMonitorUnresponsive(int pid, String reason) {
-        mWindowManagerCallbacks.notifyGestureMonitorUnresponsive(pid, reason);
-    }
-
-    // Native callback
-    @SuppressWarnings("unused")
-    private void notifyWindowResponsive(IBinder token) {
-        int gestureMonitorPid = -1;
-        synchronized (mInputMonitors) {
-            final GestureMonitorSpyWindow gestureMonitor = mInputMonitors.get(token);
-            if (gestureMonitor != null) {
-                gestureMonitorPid = gestureMonitor.mWindowHandle.ownerPid;
-            }
-        }
-        if (gestureMonitorPid != -1) {
-            mWindowManagerCallbacks.notifyGestureMonitorResponsive(gestureMonitorPid);
-            return;
-        }
-        mWindowManagerCallbacks.notifyWindowResponsive(token);
-    }
-
-    // Native callback
-    @SuppressWarnings("unused")
-    private void notifyMonitorResponsive(int pid) {
-        mWindowManagerCallbacks.notifyGestureMonitorResponsive(pid);
+    private void notifyWindowResponsive(IBinder token, int pid, boolean isPidValid) {
+        mWindowManagerCallbacks.notifyWindowResponsive(token,
+                isPidValid ? OptionalInt.of(pid) : OptionalInt.empty());
     }
 
     // Native callback.
@@ -3329,34 +3299,22 @@
         void notifyNoFocusedWindowAnr(InputApplicationHandle applicationHandle);
 
         /**
-         * Notify the window manager about a gesture monitor that is unresponsive.
-         *
-         * @param pid the pid of the gesture monitor process
-         * @param reason the reason why this connection is unresponsive
-         */
-        void notifyGestureMonitorUnresponsive(int pid, @NonNull String reason);
-
-        /**
          * Notify the window manager about a window that is unresponsive.
          *
          * @param token the token that can be used to look up the window
+         * @param pid the pid of the window owner, if known
          * @param reason the reason why this connection is unresponsive
          */
-        void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull String reason);
-
-        /**
-         * Notify the window manager about a gesture monitor that has become responsive.
-         *
-         * @param pid the pid of the gesture monitor process
-         */
-        void notifyGestureMonitorResponsive(int pid);
+        void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid,
+                @NonNull String reason);
 
         /**
          * Notify the window manager about a window that has become responsive.
          *
          * @param token the token that can be used to look up the window
+         * @param pid the pid of the window owner, if known
          */
-        void notifyWindowResponsive(@NonNull IBinder token);
+        void notifyWindowResponsive(@NonNull IBinder token, @NonNull OptionalInt pid);
 
         /**
          * This callback is invoked when an event first arrives to InputDispatcher and before it is
diff --git a/services/core/java/com/android/server/inputmethod/ImePlatformCompatUtils.java b/services/core/java/com/android/server/inputmethod/ImePlatformCompatUtils.java
new file mode 100644
index 0000000..83ca16d
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/ImePlatformCompatUtils.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static android.inputmethodservice.InputMethodService.FINISH_INPUT_NO_FALLBACK_CONNECTION;
+import static android.view.inputmethod.InputMethodManager.CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import com.android.internal.compat.IPlatformCompat;
+
+/**
+ * A utility class used by {@link InputMethodManagerService} to manage the platform
+ * compatibility changes on IMF (Input Method Framework) side.
+ */
+final class ImePlatformCompatUtils {
+    private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
+            ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+
+    /**
+     * Whether to finish the {@link android.view.inputmethod.InputConnection} when the device
+     * becomes {@link android.os.PowerManager#isInteractive non-interactive}.
+     *
+     * @param imeUid The uid of the IME application
+     */
+    public boolean shouldFinishInputWithReportToIme(int imeUid) {
+        return isChangeEnabledByUid(FINISH_INPUT_NO_FALLBACK_CONNECTION, imeUid);
+    }
+
+    /**
+     *  Whether to clear {@link android.view.inputmethod.InputMethodManager#SHOW_FORCED} flag
+     *  when the next IME focused application changed.
+     *
+     * @param clientUid The uid of the app
+     */
+    public boolean shouldClearShowForcedFlag(int clientUid) {
+        return isChangeEnabledByUid(CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING, clientUid);
+    }
+
+    private boolean isChangeEnabledByUid(long changeFlag, int uid) {
+        boolean result = false;
+        try {
+            result = mPlatformCompat.isChangeEnabledByUid(changeFlag, uid);
+        } catch (RemoteException e) {
+        }
+        return result;
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 0b7e391..eb1de2a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -15,7 +15,6 @@
 
 package com.android.server.inputmethod;
 
-import static android.inputmethodservice.InputMethodService.FINISH_INPUT_NO_FALLBACK_CONNECTION;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
 import static android.os.IServiceManager.DUMP_FLAG_PROTO;
@@ -148,7 +147,6 @@
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.inputmethod.DirectBootAwareness;
@@ -279,6 +277,7 @@
     final WindowManagerInternal mWindowManagerInternal;
     final PackageManagerInternal mPackageManagerInternal;
     final InputManagerInternal mInputManagerInternal;
+    final ImePlatformCompatUtils mImePlatformCompatUtils;
     final boolean mHasFeature;
     private final ArrayMap<String, List<InputMethodSubtype>> mAdditionalSubtypeMap =
             new ArrayMap<>();
@@ -691,8 +690,6 @@
      */
     boolean mIsInteractive = true;
 
-    private final IPlatformCompat mPlatformCompat;
-
     int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
 
     /**
@@ -1627,6 +1624,7 @@
         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
+        mImePlatformCompatUtils = new ImePlatformCompatUtils();
         mImeDisplayValidator = mWindowManagerInternal::getDisplayImePolicy;
         mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
         mUserManager = mContext.getSystemService(UserManager.class);
@@ -1634,8 +1632,7 @@
         mAccessibilityManager = AccessibilityManager.getInstance(context);
         mHasFeature = context.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_INPUT_METHODS);
-        mPlatformCompat = IPlatformCompat.Stub.asInterface(
-                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+
         mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
 
         Bundle extras = new Bundle();
@@ -3620,6 +3617,14 @@
             return InputBindResult.INVALID_USER;
         }
 
+        final boolean shouldClearFlag = mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.uid);
+        // In case mShowForced flag affects the next client to keep IME visible, when the current
+        // client is leaving due to the next focused client, we clear mShowForced flag when the
+        // next client's targetSdkVersion is T or higher.
+        if (mCurFocusedWindow != windowToken && mShowForced && shouldClearFlag) {
+            mShowForced = false;
+        }
+
         // cross-profile access is always allowed here to allow profile-switching.
         if (!mSettings.isCurrentProfile(userId)) {
             Slog.w(TAG, "A background user is requesting window. Hiding IME.");
@@ -4728,14 +4733,9 @@
 
             // Inform the current client of the change in active status
             if (mCurClient != null && mCurClient.client != null) {
-                boolean reportToImeController = false;
-                try {
-                    reportToImeController = mPlatformCompat.isChangeEnabledByUid(
-                            FINISH_INPUT_NO_FALLBACK_CONNECTION, getCurMethodUidLocked());
-                } catch (RemoteException e) {
-                }
                 scheduleSetActiveToClient(mCurClient, mIsInteractive, mInFullscreenMode,
-                        reportToImeController);
+                        mImePlatformCompatUtils.shouldFinishInputWithReportToIme(
+                                getCurMethodUidLocked()));
             }
         }
     }
diff --git a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
index a290eb3..ea3a3d5 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
@@ -168,6 +168,7 @@
         switch (key) {
             case AtomicFormula.PACKAGE_NAME:
             case AtomicFormula.APP_CERTIFICATE:
+            case AtomicFormula.APP_CERTIFICATE_LINEAGE:
             case AtomicFormula.INSTALLER_NAME:
             case AtomicFormula.INSTALLER_CERTIFICATE:
             case AtomicFormula.STAMP_CERTIFICATE_HASH:
diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
index 01aee7b..db81393 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
@@ -34,7 +34,6 @@
 import android.os.Binder;
 import android.os.HandlerThread;
 import android.os.LocaleList;
-import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -45,7 +44,6 @@
 import android.util.Xml;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.content.PackageMonitor;
 import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -89,32 +87,24 @@
     // SparseArray because it is more memory-efficient than a HashMap.
     private final SparseArray<StagedData> mStagedData;
 
-    private final PackageMonitor mPackageMonitor;
     private final BroadcastReceiver mUserMonitor;
 
     LocaleManagerBackupHelper(LocaleManagerService localeManagerService,
-            PackageManagerInternal pmInternal) {
+            PackageManagerInternal pmInternal, HandlerThread broadcastHandlerThread) {
         this(localeManagerService.mContext, localeManagerService, pmInternal, Clock.systemUTC(),
-                new SparseArray<>());
+                new SparseArray<>(), broadcastHandlerThread);
     }
 
     @VisibleForTesting LocaleManagerBackupHelper(Context context,
             LocaleManagerService localeManagerService,
-            PackageManagerInternal pmInternal, Clock clock, SparseArray<StagedData> stagedData) {
+            PackageManagerInternal pmInternal, Clock clock, SparseArray<StagedData> stagedData,
+            HandlerThread broadcastHandlerThread) {
         mContext = context;
         mLocaleManagerService = localeManagerService;
         mPackageManagerInternal = pmInternal;
         mClock = clock;
         mStagedData = stagedData;
 
-        HandlerThread broadcastHandlerThread = new HandlerThread(TAG,
-                Process.THREAD_PRIORITY_BACKGROUND);
-        broadcastHandlerThread.start();
-
-        mPackageMonitor = new PackageMonitorImpl();
-        mPackageMonitor.register(context, broadcastHandlerThread.getLooper(),
-                UserHandle.ALL,
-                true);
         mUserMonitor = new UserMonitor();
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_REMOVED);
@@ -127,11 +117,6 @@
         return mUserMonitor;
     }
 
-    @VisibleForTesting
-    PackageMonitor getPackageMonitor() {
-        return mPackageMonitor;
-    }
-
     /**
      * @see LocaleManagerInternal#getBackupPayload(int userId)
      */
@@ -267,6 +252,53 @@
         BackupManager.dataChanged(SYSTEM_BACKUP_PACKAGE_KEY);
     }
 
+    /**
+     * <p><b>Note:</b> This is invoked by service's common monitor
+     * {@link LocaleManagerServicePackageMonitor#onPackageAdded} when a new package is
+     * added on device.
+     */
+    void onPackageAdded(String packageName, int uid) {
+        try {
+            synchronized (mStagedDataLock) {
+                cleanStagedDataForOldEntriesLocked();
+
+                int userId = UserHandle.getUserId(uid);
+                if (mStagedData.contains(userId)) {
+                    // Perform lazy restore only if the staged data exists.
+                    doLazyRestoreLocked(packageName, userId);
+                }
+            }
+        } catch (Exception e) {
+            Slog.e(TAG, "Exception in onPackageAdded.", e);
+        }
+    }
+
+    /**
+     * <p><b>Note:</b> This is invoked by service's common monitor
+     * {@link LocaleManagerServicePackageMonitor#onPackageDataCleared} when a package's data
+     * is cleared.
+     */
+    void onPackageDataCleared() {
+        try {
+            notifyBackupManager();
+        } catch (Exception e) {
+            Slog.e(TAG, "Exception in onPackageDataCleared.", e);
+        }
+    }
+
+    /**
+     * <p><b>Note:</b> This is invoked by service's common monitor
+     * {@link LocaleManagerServicePackageMonitor#onPackageRemoved} when a package is removed
+     * from device.
+     */
+    void onPackageRemoved() {
+        try {
+            notifyBackupManager();
+        } catch (Exception e) {
+            Slog.e(TAG, "Exception in onPackageRemoved.", e);
+        }
+    }
+
     private boolean isPackageInstalledForUser(String packageName, int userId) {
         PackageInfo pkgInfo = null;
         try {
@@ -395,48 +427,6 @@
     }
 
     /**
-     * Helper to monitor package states.
-     *
-     * <p>We're interested in package added, package data cleared and package removed events.
-     */
-    private final class PackageMonitorImpl extends PackageMonitor {
-        @Override
-        public void onPackageAdded(String packageName, int uid) {
-            try {
-                synchronized (mStagedDataLock) {
-                    cleanStagedDataForOldEntriesLocked();
-
-                    int userId = UserHandle.getUserId(uid);
-                    if (mStagedData.contains(userId)) {
-                        // Perform lazy restore only if the staged data exists.
-                        doLazyRestoreLocked(packageName, userId);
-                    }
-                }
-            } catch (Exception e) {
-                Slog.e(TAG, "Exception in onPackageAdded.", e);
-            }
-        }
-
-        @Override
-        public void onPackageDataCleared(String packageName, int uid) {
-            try {
-                notifyBackupManager();
-            } catch (Exception e) {
-                Slog.e(TAG, "Exception in onPackageDataCleared.", e);
-            }
-        }
-
-        @Override
-        public void onPackageRemoved(String packageName, int uid) {
-            try {
-                notifyBackupManager();
-            } catch (Exception e) {
-                Slog.e(TAG, "Exception in onPackageRemoved.", e);
-            }
-        }
-    }
-
-    /**
      * Performs lazy restore from the staged data.
      *
      * <p>This is invoked by the package monitor on the package added callback.
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index d459f8d..c427705 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -29,6 +29,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
+import android.os.HandlerThread;
 import android.os.LocaleList;
 import android.os.Process;
 import android.os.RemoteException;
@@ -38,14 +39,13 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.DumpUtils;
+import com.android.internal.content.PackageMonitor;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
 import java.io.FileDescriptor;
-import java.io.PrintWriter;
 
 /**
  * The implementation of ILocaleManager.aidl.
@@ -62,6 +62,8 @@
 
     private LocaleManagerBackupHelper mBackupHelper;
 
+    private final PackageMonitor mPackageMonitor;
+
     public static final boolean DEBUG = false;
 
     public LocaleManagerService(Context context) {
@@ -71,15 +73,26 @@
         mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+
+        HandlerThread broadcastHandlerThread = new HandlerThread(TAG,
+                Process.THREAD_PRIORITY_BACKGROUND);
+        broadcastHandlerThread.start();
+
         mBackupHelper = new LocaleManagerBackupHelper(this,
-                mPackageManagerInternal);
+                mPackageManagerInternal, broadcastHandlerThread);
+
+        mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper);
+        mPackageMonitor.register(context, broadcastHandlerThread.getLooper(),
+                UserHandle.ALL,
+                true);
     }
 
     @VisibleForTesting
     LocaleManagerService(Context context, ActivityTaskManagerInternal activityTaskManagerInternal,
             ActivityManagerInternal activityManagerInternal,
             PackageManagerInternal packageManagerInternal,
-            LocaleManagerBackupHelper localeManagerBackupHelper) {
+            LocaleManagerBackupHelper localeManagerBackupHelper,
+            PackageMonitor packageMonitor) {
         super(context);
         mContext = context;
         mBinderService = new LocaleManagerBinderService();
@@ -87,6 +100,7 @@
         mActivityManagerInternal = activityManagerInternal;
         mPackageManagerInternal = packageManagerInternal;
         mBackupHelper = localeManagerBackupHelper;
+        mPackageMonitor = packageMonitor;
     }
 
     @Override
@@ -130,11 +144,6 @@
         }
 
         @Override
-        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            LocaleManagerService.this.dump(fd, pw, args);
-        }
-
-        @Override
         public void onShellCommand(FileDescriptor in, FileDescriptor out,
                 FileDescriptor err, String[] args, ShellCallback callback,
                 ResultReceiver resultReceiver) {
@@ -407,14 +416,6 @@
         return null;
     }
 
-    /**
-     * Dumps useful info related to service.
-     */
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
-        // TODO(b/201766221): Implement when there is state.
-    }
-
     private void logMetric(@NonNull AppLocaleChangedAtomRecord atomRecordForMetrics) {
         FrameworkStatsLog.write(FrameworkStatsLog.APPLICATION_LOCALES_CHANGED,
                 atomRecordForMetrics.mCallingUid,
diff --git a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
new file mode 100644
index 0000000..b459be7
--- /dev/null
+++ b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locales;
+
+import com.android.internal.content.PackageMonitor;
+
+/**
+ * Helper to monitor package states inside {@link LocaleManagerService}.
+ *
+ * <p> These listeners forward the call to different aspects of locale service that
+ * handle the business logic.
+ * <p> We're interested in package added, package data cleared and package removed events.
+ */
+final class LocaleManagerServicePackageMonitor extends PackageMonitor {
+    private LocaleManagerBackupHelper mBackupHelper;
+
+    LocaleManagerServicePackageMonitor(LocaleManagerBackupHelper localeManagerBackupHelper) {
+        mBackupHelper = localeManagerBackupHelper;
+    }
+
+    @Override
+    public void onPackageAdded(String packageName, int uid) {
+        mBackupHelper.onPackageAdded(packageName, uid);
+    }
+
+    @Override
+    public void onPackageDataCleared(String packageName, int uid) {
+        mBackupHelper.onPackageDataCleared();
+    }
+
+    @Override
+    public void onPackageRemoved(String packageName, int uid) {
+        mBackupHelper.onPackageRemoved();
+    }
+}
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 45d9822..fac5106 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -1127,7 +1127,7 @@
             if (provider != null && !provider.equals(manager.getName())) {
                 continue;
             }
-            CallerIdentity identity = manager.getIdentity();
+            CallerIdentity identity = manager.getProviderIdentity();
             if (identity == null) {
                 continue;
             }
@@ -1149,7 +1149,7 @@
             return Collections.emptyList();
         }
 
-        CallerIdentity identity = manager.getIdentity();
+        CallerIdentity identity = manager.getProviderIdentity();
         if (identity == null) {
             return Collections.emptyList();
         }
@@ -1536,7 +1536,7 @@
         if (!enabled) {
             PackageTagsList.Builder builder = new PackageTagsList.Builder();
             for (LocationProviderManager manager : mProviderManagers) {
-                CallerIdentity identity = manager.getIdentity();
+                CallerIdentity identity = manager.getProviderIdentity();
                 if (identity != null) {
                     builder.add(identity.getPackageName(), identity.getAttributionTag());
                 }
@@ -1624,7 +1624,7 @@
                 if (provider != null && !provider.equals(manager.getName())) {
                     continue;
                 }
-                if (identity.equals(manager.getIdentity())) {
+                if (identity.equals(manager.getProviderIdentity())) {
                     return true;
                 }
             }
@@ -1665,7 +1665,7 @@
                 if (listener != null) {
                     ArraySet<Integer> uids = new ArraySet<>(mProviderManagers.size());
                     for (LocationProviderManager manager : mProviderManagers) {
-                        CallerIdentity identity = manager.getIdentity();
+                        CallerIdentity identity = manager.getProviderIdentity();
                         if (identity != null) {
                             uids.add(identity.getUid());
                         }
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
index 8fdde24..e9bf90f 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
@@ -16,8 +16,6 @@
 
 package com.android.server.location.contexthub;
 
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
 import android.Manifest;
 import android.content.Context;
 import android.hardware.contexthub.V1_0.AsyncEventType;
@@ -297,19 +295,14 @@
     }
 
     /**
-     * Checks for location hardware permissions.
+     * Checks for ACCESS_CONTEXT_HUB permissions.
      *
      * @param context the context of the service
      */
     /* package */
     static void checkPermissions(Context context) {
-        boolean hasAccessContextHubPermission = (context.checkCallingPermission(
-                CONTEXT_HUB_PERMISSION) == PERMISSION_GRANTED);
-
-        if (!hasAccessContextHubPermission) {
-            throw new SecurityException(
-                    "ACCESS_CONTEXT_HUB permission required to use Context Hub");
-        }
+        context.enforceCallingOrSelfPermission(CONTEXT_HUB_PERMISSION,
+                "ACCESS_CONTEXT_HUB permission required to use Context Hub");
     }
 
     /**
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 0b8f94c..acbee11 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -1468,7 +1468,7 @@
         return mProvider.getState();
     }
 
-    public @Nullable CallerIdentity getIdentity() {
+    public @Nullable CallerIdentity getProviderIdentity() {
         return mProvider.getState().identity;
     }
 
@@ -1607,7 +1607,7 @@
 
     public @Nullable Location getLastLocation(LastLocationRequest request,
             CallerIdentity identity, @PermissionLevel int permissionLevel) {
-        request = calculateLastLocationRequest(request);
+        request = calculateLastLocationRequest(request, identity);
 
         if (!isActive(request.isBypass(), identity)) {
             return null;
@@ -1636,15 +1636,16 @@
         return location;
     }
 
-    private LastLocationRequest calculateLastLocationRequest(LastLocationRequest baseRequest) {
+    private LastLocationRequest calculateLastLocationRequest(LastLocationRequest baseRequest,
+            CallerIdentity identity) {
         LastLocationRequest.Builder builder = new LastLocationRequest.Builder(baseRequest);
 
         boolean locationSettingsIgnored = baseRequest.isLocationSettingsIgnored();
         if (locationSettingsIgnored) {
             // if we are not currently allowed use location settings ignored, disable it
             if (!mSettingsHelper.getIgnoreSettingsAllowlist().contains(
-                    getIdentity().getPackageName(), getIdentity().getAttributionTag())
-                    && !mLocationManagerInternal.isProvider(null, getIdentity())) {
+                    identity.getPackageName(), identity.getAttributionTag())
+                    && !mLocationManagerInternal.isProvider(null, identity)) {
                 locationSettingsIgnored = false;
             }
 
@@ -1658,7 +1659,7 @@
                 Log.e(TAG, "adas gnss bypass request received in non-gps provider");
                 adasGnssBypass = false;
             } else if (!mLocationSettings.getUserSettings(
-                    getIdentity().getUserId()).isAdasGnssLocationEnabled()) {
+                    identity.getUserId()).isAdasGnssLocationEnabled()) {
                 adasGnssBypass = false;
             }
 
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 96391ac..92703ec 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -61,6 +61,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.NoSuchElementException;
 
 /**
  * This is the system implementation of a Session. Apps will interact with the
@@ -647,18 +648,17 @@
             if (mDestroyed) {
                 return;
             }
-            toSend = new ArrayList<>();
-            if (mQueue != null) {
-                toSend.ensureCapacity(mQueue.size());
-                toSend.addAll(mQueue);
-            }
+            toSend = mQueue == null ? null : new ArrayList<>(mQueue);
         }
         Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
         for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
-            ParceledListSlice<QueueItem> parcelableQueue = new ParceledListSlice<>(toSend);
-            // Limit the size of initial Parcel to prevent binder buffer overflow
-            // as onQueueChanged is an async binder call.
-            parcelableQueue.setInlineCountLimit(1);
+            ParceledListSlice<QueueItem> parcelableQueue = null;
+            if (toSend != null) {
+                parcelableQueue = new ParceledListSlice<>(toSend);
+                // Limit the size of initial Parcel to prevent binder buffer overflow
+                // as onQueueChanged is an async binder call.
+                parcelableQueue.setInlineCountLimit(1);
+            }
 
             try {
                 holder.mCallback.onQueueChanged(parcelableQueue);
@@ -793,7 +793,10 @@
         }
         for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
             try {
+                holder.mCallback.asBinder().unlinkToDeath(holder.mDeathMonitor, 0);
                 holder.mCallback.onSessionDestroyed();
+            } catch (NoSuchElementException e) {
+                logCallbackException("error unlinking to binder death", holder, e);
             } catch (DeadObjectException e) {
                 logCallbackException("Removing dead callback in pushSessionDestroyed", holder, e);
             } catch (RemoteException e) {
@@ -1376,12 +1379,22 @@
                     return;
                 }
                 if (getControllerHolderIndexForCb(cb) < 0) {
-                    mControllerCallbackHolders.add(new ISessionControllerCallbackHolder(cb,
-                            packageName, Binder.getCallingUid()));
+                    ISessionControllerCallbackHolder holder = new ISessionControllerCallbackHolder(
+                        cb, packageName, Binder.getCallingUid(), () -> unregisterCallback(cb));
+                    mControllerCallbackHolders.add(holder);
                     if (DEBUG) {
                         Log.d(TAG, "registering controller callback " + cb + " from controller"
                                 + packageName);
                     }
+                    // Avoid callback leaks
+                    try {
+                        // cb is not referenced outside of the MediaSessionRecord, so the death
+                        // handler won't prevent MediaSessionRecord to be garbage collected.
+                        cb.asBinder().linkToDeath(holder.mDeathMonitor, 0);
+                    } catch (RemoteException e) {
+                        unregisterCallback(cb);
+                        Log.w(TAG, "registerCallback failed to linkToDeath", e);
+                    }
                 }
             }
         }
@@ -1391,6 +1404,12 @@
             synchronized (mLock) {
                 int index = getControllerHolderIndexForCb(cb);
                 if (index != -1) {
+                    try {
+                        cb.asBinder().unlinkToDeath(
+                          mControllerCallbackHolders.get(index).mDeathMonitor, 0);
+                    } catch (NoSuchElementException e) {
+                        Log.w(TAG, "error unlinking to binder death", e);
+                    }
                     mControllerCallbackHolders.remove(index);
                 }
                 if (DEBUG) {
@@ -1601,12 +1620,14 @@
         private final ISessionControllerCallback mCallback;
         private final String mPackageName;
         private final int mUid;
+        private final IBinder.DeathRecipient mDeathMonitor;
 
         ISessionControllerCallbackHolder(ISessionControllerCallback callback, String packageName,
-                int uid) {
+                int uid, IBinder.DeathRecipient deathMonitor) {
             mCallback = callback;
             mPackageName = packageName;
             mUid = uid;
+            mDeathMonitor = deathMonitor;
         }
     }
 
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index 851ea3d..1b7d1ba 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -293,7 +293,7 @@
                         .addAction(R.drawable.ic_menu_refresh, mContext.getString(R.string.reset),
                                 mResetIntent)
                         .setColor(mContext.getColor(
-                                com.android.internal.R.color.system_notification_accent_color));
+                                android.R.color.system_notification_accent_color));
 
         mNotificationManager.notify(null /* tag */, SystemMessage.NOTE_VPN_STATUS,
                 builder.build());
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index 33ac6cd..c963154 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -77,6 +77,8 @@
     private static final int EVENT_FIREWALL_CHAIN_ENABLED = 12;
     private static final int EVENT_UPDATE_METERED_RESTRICTED_PKGS = 13;
     private static final int EVENT_APP_IDLE_WL_CHANGED = 14;
+    private static final int EVENT_METERED_ALLOWLIST_CHANGED = 15;
+    private static final int EVENT_METERED_DENYLIST_CHANGED = 16;
 
     private final LogBuffer mNetworkBlockedBuffer = new LogBuffer(MAX_NETWORK_BLOCKED_LOG_SIZE);
     private final LogBuffer mUidStateChangeBuffer = new LogBuffer(MAX_LOG_SIZE);
@@ -89,7 +91,7 @@
     void networkBlocked(int uid, @Nullable UidBlockedState uidBlockedState) {
         synchronized (mLock) {
             if (LOGD || uid == mDebugUid) {
-                Slog.d(TAG, "Blocked state of uid: " + uidBlockedState.toString());
+                Slog.d(TAG, "Blocked state of " + uid + ": " + uidBlockedState.toString());
             }
             if (uidBlockedState == null) {
                 mNetworkBlockedBuffer.networkBlocked(uid, BLOCKED_REASON_NONE, ALLOWED_REASON_NONE,
@@ -245,6 +247,24 @@
         }
     }
 
+    void meteredAllowlistChanged(int uid, boolean added) {
+        synchronized (mLock) {
+            if (LOGD || mDebugUid == uid) {
+                Slog.d(TAG, getMeteredAllowlistChangedLog(uid, added));
+            }
+            mEventsBuffer.meteredAllowlistChanged(uid, added);
+        }
+    }
+
+    void meteredDenylistChanged(int uid, boolean added) {
+        synchronized (mLock) {
+            if (LOGD || mDebugUid == uid) {
+                Slog.d(TAG, getMeteredDenylistChangedLog(uid, added));
+            }
+            mEventsBuffer.meteredDenylistChanged(uid, added);
+        }
+    }
+
     void setDebugUid(int uid) {
         mDebugUid = uid;
     }
@@ -320,6 +340,14 @@
         return "Firewall chain " + getFirewallChainName(chain) + " state: " + enabled;
     }
 
+    private static String getMeteredAllowlistChangedLog(int uid, boolean added) {
+        return "metered-allowlist for " + uid + " changed to " + added;
+    }
+
+    private static String getMeteredDenylistChangedLog(int uid, boolean added) {
+        return "metered-denylist for " + uid + " changed to " + added;
+    }
+
     private static String getFirewallChainName(int chain) {
         switch (chain) {
             case FIREWALL_CHAIN_DOZABLE:
@@ -520,6 +548,28 @@
             data.timeStamp = System.currentTimeMillis();
         }
 
+        public void meteredAllowlistChanged(int uid, boolean added) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_METERED_ALLOWLIST_CHANGED;
+            data.ifield1 = uid;
+            data.bfield1 = added;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void meteredDenylistChanged(int uid, boolean added) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_METERED_DENYLIST_CHANGED;
+            data.ifield1 = uid;
+            data.bfield1 = added;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
         public void reverseDump(IndentingPrintWriter pw) {
             final Data[] allData = toArray();
             for (int i = allData.length - 1; i >= 0; --i) {
@@ -567,6 +617,10 @@
                     return getUidFirewallRuleChangedLog(data.ifield1, data.ifield2, data.ifield3);
                 case EVENT_FIREWALL_CHAIN_ENABLED:
                     return getFirewallChainEnabledLog(data.ifield1, data.bfield1);
+                case EVENT_METERED_ALLOWLIST_CHANGED:
+                    return getMeteredAllowlistChangedLog(data.ifield1, data.bfield1);
+                case EVENT_METERED_DENYLIST_CHANGED:
+                    return getMeteredDenylistChangedLog(data.ifield1, data.bfield1);
                 default:
                     return String.valueOf(data.type);
             }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 60962b1..48ad22c 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -4864,7 +4864,7 @@
                     + ", isAllowed=" + isAllowed
                     + ", isRestrictedByAdmin=" + isRestrictedByAdmin
                     + ", oldBlockedState=" + previousUidBlockedState.toString()
-                    + ", newBlockedState="
+                    + ", newBlockedState=" + uidBlockedState.toString()
                     + ", oldBlockedMeteredReasons=" + NetworkPolicyManager.blockedReasonsToString(
                     uidBlockedState.blockedReasons & BLOCKED_METERED_REASON_MASK)
                     + ", oldBlockedMeteredEffectiveReasons="
@@ -5420,6 +5420,7 @@
         if (LOGV) Slog.v(TAG, "setMeteredNetworkDenylist " + uid + ": " + enable);
         try {
             mNetworkManager.setUidOnMeteredNetworkDenylist(uid, enable);
+            mLogger.meteredAllowlistChanged(uid, enable);
         } catch (IllegalStateException e) {
             Log.wtf(TAG, "problem setting denylist (" + enable + ") rules for " + uid, e);
         } catch (RemoteException e) {
@@ -5431,6 +5432,7 @@
         if (LOGV) Slog.v(TAG, "setMeteredNetworkAllowlist " + uid + ": " + enable);
         try {
             mNetworkManager.setUidOnMeteredNetworkAllowlist(uid, enable);
+            mLogger.meteredDenylistChanged(uid, enable);
         } catch (IllegalStateException e) {
             Log.wtf(TAG, "problem setting allowlist (" + enable + ") rules for " + uid, e);
         } catch (RemoteException e) {
@@ -5563,7 +5565,9 @@
                     .setFirewallUidRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid,
                             FIREWALL_RULE_DEFAULT);
             mNetworkManager.setUidOnMeteredNetworkAllowlist(uid, false);
+            mLogger.meteredAllowlistChanged(uid, false);
             mNetworkManager.setUidOnMeteredNetworkDenylist(uid, false);
+            mLogger.meteredDenylistChanged(uid, false);
         } catch (IllegalStateException e) {
             Log.wtf(TAG, "problem resetting firewall uid rules for " + uid, e);
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
index a9b2570..e09f7b0 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
@@ -40,11 +40,12 @@
 import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
 import java.util.Iterator;
-import java.util.LinkedList;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -76,7 +77,7 @@
     private final Handler mFileWriteHandler;
     @VisibleForTesting
     // List of files holding history information, sorted newest to oldest
-    final LinkedList<AtomicFile> mHistoryFiles;
+    final List<AtomicFile> mHistoryFiles;
     private final File mHistoryDir;
     private final File mVersionFile;
     // Current version of the database files schema
@@ -94,7 +95,7 @@
         mFileWriteHandler = fileWriteHandler;
         mVersionFile = new File(dir, "version");
         mHistoryDir = new File(dir, "history");
-        mHistoryFiles = new LinkedList<>();
+        mHistoryFiles = new ArrayList<>();
         mBuffer = new NotificationHistory();
         mWriteBufferRunnable = new WriteBufferRunnable();
 
@@ -133,7 +134,7 @@
                 safeParseLong(lhs.getName())));
 
         for (File file : files) {
-            mHistoryFiles.addLast(new AtomicFile(file));
+            mHistoryFiles.add(new AtomicFile(file));
         }
     }
 
@@ -411,7 +412,7 @@
                         + file.getBaseFile().getAbsolutePath());
                 try {
                     writeLocked(file, mBuffer);
-                    mHistoryFiles.addFirst(file);
+                    mHistoryFiles.add(0, file);
                     mBuffer = new NotificationHistory();
 
                     scheduleDeletion(file.getBaseFile(), time, HISTORY_RETENTION_DAYS);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 16b5fb1..a711b44 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -775,7 +775,7 @@
 
         ArraySet<String> defaultDnds = mConditionProviders.getDefaultPackages();
         for (int i = 0; i < defaultDnds.size(); i++) {
-            allowDndPackage(defaultDnds.valueAt(i));
+            allowDndPackage(userId, defaultDnds.valueAt(i));
         }
 
         setDefaultAssistantForUser(userId);
@@ -875,9 +875,9 @@
         }
     }
 
-    private void allowDndPackage(String packageName) {
+    private void allowDndPackage(int userId, String packageName) {
         try {
-            getBinderService().setNotificationPolicyAccessGranted(packageName, true);
+            getBinderService().setNotificationPolicyAccessGrantedForUser(packageName, userId, true);
         } catch (RemoteException e) {
             e.printStackTrace();
         }
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 66c7c50..bbdea32 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -478,6 +478,7 @@
         pw.println(prefix + "opPkg=" + getSbn().getOpPkg());
         pw.println(prefix + "icon=" + notification.getSmallIcon());
         pw.println(prefix + "flags=0x" + Integer.toHexString(notification.flags));
+        pw.println(prefix + "originalFlags=0x" + Integer.toHexString(mOriginalFlags));
         pw.println(prefix + "pri=" + notification.priority);
         pw.println(prefix + "key=" + getSbn().getKey());
         pw.println(prefix + "seen=" + mStats.hasSeen());
@@ -544,6 +545,7 @@
         if (notification == null) {
             pw.println(prefix + "None");
             return;
+
         }
         pw.println(prefix + "fullscreenIntent=" + notification.fullScreenIntent);
         pw.println(prefix + "contentIntent=" + notification.contentIntent);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 4387249..cc93d4c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -330,6 +330,7 @@
     static public final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
     static public final String SYSTEM_DIALOG_REASON_ASSIST = "assist";
     static public final String SYSTEM_DIALOG_REASON_SCREENSHOT = "screenshot";
+    static public final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav";
 
     private static final String TALKBACK_LABEL = "TalkBack";
 
diff --git a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
new file mode 100644
index 0000000..3550bda
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.util.Slog;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represent a step on a single vibrator that plays one or more segments from a
+ * {@link VibrationEffect.Composed} effect.
+ */
+abstract class AbstractVibratorStep extends Step {
+    public final VibratorController controller;
+    public final VibrationEffect.Composed effect;
+    public final int segmentIndex;
+    public final long previousStepVibratorOffTimeout;
+
+    long mVibratorOnResult;
+    boolean mVibratorCompleteCallbackReceived;
+
+    /**
+     * @param conductor          The VibrationStepConductor for these steps.
+     * @param startTime          The time to schedule this step in the
+     *                           {@link VibrationStepConductor}.
+     * @param controller         The vibrator that is playing the effect.
+     * @param effect             The effect being played in this step.
+     * @param index              The index of the next segment to be played by this step
+     * @param previousStepVibratorOffTimeout The time the vibrator is expected to complete any
+     *                           previous vibration and turn off. This is used to allow this step to
+     *                           be triggered when the completion callback is received, and can
+     *                           be used to play effects back-to-back.
+     */
+    AbstractVibratorStep(VibrationStepConductor conductor, long startTime,
+            VibratorController controller, VibrationEffect.Composed effect, int index,
+            long previousStepVibratorOffTimeout) {
+        super(conductor, startTime);
+        this.controller = controller;
+        this.effect = effect;
+        this.segmentIndex = index;
+        this.previousStepVibratorOffTimeout = previousStepVibratorOffTimeout;
+    }
+
+    public int getVibratorId() {
+        return controller.getVibratorInfo().getId();
+    }
+
+    @Override
+    public long getVibratorOnDuration() {
+        return mVibratorOnResult;
+    }
+
+    @Override
+    public boolean acceptVibratorCompleteCallback(int vibratorId) {
+        boolean isSameVibrator = controller.getVibratorInfo().getId() == vibratorId;
+        mVibratorCompleteCallbackReceived |= isSameVibrator;
+        // Only activate this step if a timeout was set to wait for the vibration to complete,
+        // otherwise we are waiting for the correct time to play the next step.
+        return isSameVibrator && (previousStepVibratorOffTimeout > SystemClock.uptimeMillis());
+    }
+
+    @Override
+    public List<Step> cancel() {
+        return Arrays.asList(new CompleteEffectVibratorStep(conductor, SystemClock.uptimeMillis(),
+                /* cancelled= */ true, controller, previousStepVibratorOffTimeout));
+    }
+
+    @Override
+    public void cancelImmediately() {
+        if (previousStepVibratorOffTimeout > SystemClock.uptimeMillis()) {
+            // Vibrator might be running from previous steps, so turn it off while canceling.
+            stopVibrating();
+        }
+    }
+
+    protected void stopVibrating() {
+        if (VibrationThread.DEBUG) {
+            Slog.d(VibrationThread.TAG,
+                    "Turning off vibrator " + getVibratorId());
+        }
+        controller.off();
+    }
+
+    protected void changeAmplitude(float amplitude) {
+        if (VibrationThread.DEBUG) {
+            Slog.d(VibrationThread.TAG,
+                    "Amplitude changed on vibrator " + getVibratorId() + " to " + amplitude);
+        }
+        controller.setAmplitude(amplitude);
+    }
+
+    /**
+     * Return the {@link VibrationStepConductor#nextVibrateStep} with same timings, only jumping
+     * the segments.
+     */
+    protected List<Step> skipToNextSteps(int segmentsSkipped) {
+        return nextSteps(startTime, previousStepVibratorOffTimeout, segmentsSkipped);
+    }
+
+    /**
+     * Return the {@link VibrationStepConductor#nextVibrateStep} with same start and off timings
+     * calculated from {@link #getVibratorOnDuration()}, jumping all played segments.
+     *
+     * <p>This method has same behavior as {@link #skipToNextSteps(int)} when the vibrator
+     * result is non-positive, meaning the vibrator has either ignored or failed to turn on.
+     */
+    protected List<Step> nextSteps(int segmentsPlayed) {
+        if (mVibratorOnResult <= 0) {
+            // Vibration was not started, so just skip the played segments and keep timings.
+            return skipToNextSteps(segmentsPlayed);
+        }
+        long nextStartTime = SystemClock.uptimeMillis() + mVibratorOnResult;
+        long nextVibratorOffTimeout =
+                nextStartTime + VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
+        return nextSteps(nextStartTime, nextVibratorOffTimeout, segmentsPlayed);
+    }
+
+    /**
+     * Return the {@link VibrationStepConductor#nextVibrateStep} with given start and off timings,
+     * which might be calculated independently, jumping all played segments.
+     *
+     * <p>This should be used when the vibrator on/off state is not responsible for the steps
+     * execution timings, e.g. while playing the vibrator amplitudes.
+     */
+    protected List<Step> nextSteps(long nextStartTime, long vibratorOffTimeout,
+            int segmentsPlayed) {
+        Step nextStep = conductor.nextVibrateStep(nextStartTime, controller, effect,
+                segmentIndex + segmentsPlayed, vibratorOffTimeout);
+        return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST : Arrays.asList(nextStep);
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java b/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
new file mode 100644
index 0000000..8585e34
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.VibrationEffect;
+import android.util.Slog;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a step to complete a {@link VibrationEffect}.
+ *
+ * <p>This runs right at the time the vibration is considered to end and will update the pending
+ * vibrators count. This can turn off the vibrator or slowly ramp it down to zero amplitude.
+ */
+final class CompleteEffectVibratorStep extends AbstractVibratorStep {
+    private final boolean mCancelled;
+
+    CompleteEffectVibratorStep(VibrationStepConductor conductor, long startTime, boolean cancelled,
+            VibratorController controller, long previousStepVibratorOffTimeout) {
+        super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1,
+                previousStepVibratorOffTimeout);
+        mCancelled = cancelled;
+    }
+
+    @Override
+    public boolean isCleanUp() {
+        // If the vibration was cancelled then this is just a clean up to ramp off the vibrator.
+        // Otherwise this step is part of the vibration.
+        return mCancelled;
+    }
+
+    @Override
+    public List<Step> cancel() {
+        if (mCancelled) {
+            // Double cancelling will just turn off the vibrator right away.
+            return Arrays.asList(
+                    new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(), controller));
+        }
+        return super.cancel();
+    }
+
+    @Override
+    public List<Step> play() {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "CompleteEffectVibratorStep");
+        try {
+            if (VibrationThread.DEBUG) {
+                Slog.d(VibrationThread.TAG,
+                        "Running " + (mCancelled ? "cancel" : "complete") + " vibration"
+                                + " step on vibrator " + controller.getVibratorInfo().getId());
+            }
+            if (mVibratorCompleteCallbackReceived) {
+                // Vibration completion callback was received by this step, just turn if off
+                // and skip any clean-up.
+                stopVibrating();
+                return VibrationStepConductor.EMPTY_STEP_LIST;
+            }
+
+            float currentAmplitude = controller.getCurrentAmplitude();
+            long remainingOnDuration =
+                    previousStepVibratorOffTimeout - VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT
+                            - SystemClock.uptimeMillis();
+            long rampDownDuration =
+                    Math.min(remainingOnDuration,
+                            conductor.vibrationSettings.getRampDownDuration());
+            long stepDownDuration = conductor.vibrationSettings.getRampStepDuration();
+            if (currentAmplitude < VibrationStepConductor.RAMP_OFF_AMPLITUDE_MIN
+                    || rampDownDuration <= stepDownDuration) {
+                // No need to ramp down the amplitude, just wait to turn it off.
+                if (mCancelled) {
+                    // Vibration is completing because it was cancelled, turn off right away.
+                    stopVibrating();
+                    return VibrationStepConductor.EMPTY_STEP_LIST;
+                } else {
+                    return Arrays.asList(new TurnOffVibratorStep(
+                            conductor, previousStepVibratorOffTimeout, controller));
+                }
+            }
+
+            if (VibrationThread.DEBUG) {
+                Slog.d(VibrationThread.TAG,
+                        "Ramping down vibrator " + controller.getVibratorInfo().getId()
+                                + " from amplitude " + currentAmplitude
+                                + " for " + rampDownDuration + "ms");
+            }
+            float amplitudeDelta = currentAmplitude / (rampDownDuration / stepDownDuration);
+            float amplitudeTarget = currentAmplitude - amplitudeDelta;
+            long newVibratorOffTimeout =
+                    mCancelled ? rampDownDuration : previousStepVibratorOffTimeout;
+            return Arrays.asList(
+                    new RampOffVibratorStep(conductor, startTime, amplitudeTarget, amplitudeDelta,
+                            controller, newVibratorOffTimeout));
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
new file mode 100644
index 0000000..d1ea805
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.Trace;
+import android.os.VibrationEffect;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a step to turn the vibrator on using a composition of primitives.
+ *
+ * <p>This step will use the maximum supported number of consecutive segments of type
+ * {@link PrimitiveSegment} starting at the current index.
+ */
+final class ComposePrimitivesVibratorStep extends AbstractVibratorStep {
+
+    ComposePrimitivesVibratorStep(VibrationStepConductor conductor, long startTime,
+            VibratorController controller, VibrationEffect.Composed effect, int index,
+            long previousStepVibratorOffTimeout) {
+        // This step should wait for the last vibration to finish (with the timeout) and for the
+        // intended step start time (to respect the effect delays).
+        super(conductor, Math.max(startTime, previousStepVibratorOffTimeout), controller, effect,
+                index, previousStepVibratorOffTimeout);
+    }
+
+    @Override
+    public List<Step> play() {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePrimitivesStep");
+        try {
+            // Load the next PrimitiveSegments to create a single compose call to the vibrator,
+            // limited to the vibrator composition maximum size.
+            int limit = controller.getVibratorInfo().getCompositionSizeMax();
+            int segmentCount = limit > 0
+                    ? Math.min(effect.getSegments().size(), segmentIndex + limit)
+                    : effect.getSegments().size();
+            List<PrimitiveSegment> primitives = new ArrayList<>();
+            for (int i = segmentIndex; i < segmentCount; i++) {
+                VibrationEffectSegment segment = effect.getSegments().get(i);
+                if (segment instanceof PrimitiveSegment) {
+                    primitives.add((PrimitiveSegment) segment);
+                } else {
+                    break;
+                }
+            }
+
+            if (primitives.isEmpty()) {
+                Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePrimitivesStep: "
+                        + effect.getSegments().get(segmentIndex));
+                return skipToNextSteps(/* segmentsSkipped= */ 1);
+            }
+
+            if (VibrationThread.DEBUG) {
+                Slog.d(VibrationThread.TAG, "Compose " + primitives + " primitives on vibrator "
+                        + controller.getVibratorInfo().getId());
+            }
+            mVibratorOnResult = controller.on(
+                    primitives.toArray(new PrimitiveSegment[primitives.size()]),
+                    getVibration().id);
+
+            return nextSteps(/* segmentsPlayed= */ primitives.size());
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
new file mode 100644
index 0000000..73bf933
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.Trace;
+import android.os.VibrationEffect;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a step to turn the vibrator on using a composition of PWLE segments.
+ *
+ * <p>This step will use the maximum supported number of consecutive segments of type
+ * {@link StepSegment} or {@link RampSegment} starting at the current index.
+ */
+final class ComposePwleVibratorStep extends AbstractVibratorStep {
+
+    ComposePwleVibratorStep(VibrationStepConductor conductor, long startTime,
+            VibratorController controller, VibrationEffect.Composed effect, int index,
+            long previousStepVibratorOffTimeout) {
+        // This step should wait for the last vibration to finish (with the timeout) and for the
+        // intended step start time (to respect the effect delays).
+        super(conductor, Math.max(startTime, previousStepVibratorOffTimeout), controller, effect,
+                index, previousStepVibratorOffTimeout);
+    }
+
+    @Override
+    public List<Step> play() {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePwleStep");
+        try {
+            // Load the next RampSegments to create a single composePwle call to the vibrator,
+            // limited to the vibrator PWLE maximum size.
+            int limit = controller.getVibratorInfo().getPwleSizeMax();
+            int segmentCount = limit > 0
+                    ? Math.min(effect.getSegments().size(), segmentIndex + limit)
+                    : effect.getSegments().size();
+            List<RampSegment> pwles = new ArrayList<>();
+            for (int i = segmentIndex; i < segmentCount; i++) {
+                VibrationEffectSegment segment = effect.getSegments().get(i);
+                if (segment instanceof RampSegment) {
+                    pwles.add((RampSegment) segment);
+                } else {
+                    break;
+                }
+            }
+
+            if (pwles.isEmpty()) {
+                Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePwleStep: "
+                        + effect.getSegments().get(segmentIndex));
+                return skipToNextSteps(/* segmentsSkipped= */ 1);
+            }
+
+            if (VibrationThread.DEBUG) {
+                Slog.d(VibrationThread.TAG, "Compose " + pwles + " PWLEs on vibrator "
+                        + controller.getVibratorInfo().getId());
+            }
+            mVibratorOnResult = controller.on(pwles.toArray(new RampSegment[pwles.size()]),
+                    getVibration().id);
+
+            return nextSteps(/* segmentsPlayed= */ pwles.size());
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java b/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java
new file mode 100644
index 0000000..bbbca02
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.Trace;
+import android.util.Slog;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Finish a sync vibration started by a {@link StartSequentialEffectStep}.
+ *
+ * <p>This only plays after all active vibrators steps have finished, and adds a {@link
+ * StartSequentialEffectStep} to the queue if the sequential effect isn't finished yet.
+ */
+final class FinishSequentialEffectStep extends Step {
+    public final StartSequentialEffectStep startedStep;
+
+    FinishSequentialEffectStep(StartSequentialEffectStep startedStep) {
+        // No predefined startTime, just wait for all steps in the queue.
+        super(startedStep.conductor, Long.MAX_VALUE);
+        this.startedStep = startedStep;
+    }
+
+    @Override
+    public boolean isCleanUp() {
+        // This step only notes that all the vibrators has been turned off.
+        return true;
+    }
+
+    @Override
+    public List<Step> play() {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "FinishSequentialEffectStep");
+        try {
+            if (VibrationThread.DEBUG) {
+                Slog.d(VibrationThread.TAG,
+                        "FinishSequentialEffectStep for effect #" + startedStep.currentIndex);
+            }
+            conductor.vibratorManagerHooks.noteVibratorOff(conductor.getVibration().uid);
+            Step nextStep = startedStep.nextStep();
+            return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST
+                    : Arrays.asList(nextStep);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+
+    @Override
+    public List<Step> cancel() {
+        cancelImmediately();
+        return VibrationStepConductor.EMPTY_STEP_LIST;
+    }
+
+    @Override
+    public void cancelImmediately() {
+        conductor.vibratorManagerHooks.noteVibratorOff(conductor.getVibration().uid);
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
new file mode 100644
index 0000000..601ae97
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.Trace;
+import android.os.VibrationEffect;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a step to turn the vibrator on with a single prebaked effect.
+ *
+ * <p>This step automatically falls back by replacing the prebaked segment with
+ * {@link VibrationSettings#getFallbackEffect(int)}, if available.
+ */
+final class PerformPrebakedVibratorStep extends AbstractVibratorStep {
+
+    PerformPrebakedVibratorStep(VibrationStepConductor conductor, long startTime,
+            VibratorController controller, VibrationEffect.Composed effect, int index,
+            long previousStepVibratorOffTimeout) {
+        // This step should wait for the last vibration to finish (with the timeout) and for the
+        // intended step start time (to respect the effect delays).
+        super(conductor, Math.max(startTime, previousStepVibratorOffTimeout), controller, effect,
+                index, previousStepVibratorOffTimeout);
+    }
+
+    @Override
+    public List<Step> play() {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "PerformPrebakedVibratorStep");
+        try {
+            VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
+            if (!(segment instanceof PrebakedSegment)) {
+                Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a "
+                        + "PerformPrebakedVibratorStep: " + segment);
+                return skipToNextSteps(/* segmentsSkipped= */ 1);
+            }
+
+            PrebakedSegment prebaked = (PrebakedSegment) segment;
+            if (VibrationThread.DEBUG) {
+                Slog.d(VibrationThread.TAG, "Perform " + VibrationEffect.effectIdToString(
+                        prebaked.getEffectId()) + " on vibrator "
+                        + controller.getVibratorInfo().getId());
+            }
+
+            VibrationEffect fallback = getVibration().getFallback(prebaked.getEffectId());
+            mVibratorOnResult = controller.on(prebaked, getVibration().id);
+
+            if (mVibratorOnResult == 0 && prebaked.shouldFallback()
+                    && (fallback instanceof VibrationEffect.Composed)) {
+                if (VibrationThread.DEBUG) {
+                    Slog.d(VibrationThread.TAG, "Playing fallback for effect "
+                            + VibrationEffect.effectIdToString(prebaked.getEffectId()));
+                }
+                AbstractVibratorStep fallbackStep = conductor.nextVibrateStep(startTime, controller,
+                        replaceCurrentSegment((VibrationEffect.Composed) fallback),
+                        segmentIndex, previousStepVibratorOffTimeout);
+                List<Step> fallbackResult = fallbackStep.play();
+                // Update the result with the fallback result so this step is seamlessly
+                // replaced by the fallback to any outer application of this.
+                mVibratorOnResult = fallbackStep.getVibratorOnDuration();
+                return fallbackResult;
+            }
+
+            return nextSteps(/* segmentsPlayed= */ 1);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+
+    /**
+     * Replace segment at {@link #segmentIndex} in {@link #effect} with given fallback segments.
+     *
+     * @return a copy of {@link #effect} with replaced segment.
+     */
+    private VibrationEffect.Composed replaceCurrentSegment(VibrationEffect.Composed fallback) {
+        List<VibrationEffectSegment> newSegments = new ArrayList<>(effect.getSegments());
+        int newRepeatIndex = effect.getRepeatIndex();
+        newSegments.remove(segmentIndex);
+        newSegments.addAll(segmentIndex, fallback.getSegments());
+        if (segmentIndex < effect.getRepeatIndex()) {
+            newRepeatIndex += fallback.getSegments().size() - 1;
+        }
+        return new VibrationEffect.Composed(newSegments, newRepeatIndex);
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java b/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
new file mode 100644
index 0000000..8cf5fb3
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.Slog;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** Represents a step to ramp down the vibrator amplitude before turning it off. */
+final class RampOffVibratorStep extends AbstractVibratorStep {
+    private final float mAmplitudeTarget;
+    private final float mAmplitudeDelta;
+
+    RampOffVibratorStep(VibrationStepConductor conductor, long startTime, float amplitudeTarget,
+            float amplitudeDelta, VibratorController controller,
+            long previousStepVibratorOffTimeout) {
+        super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1,
+                previousStepVibratorOffTimeout);
+        mAmplitudeTarget = amplitudeTarget;
+        mAmplitudeDelta = amplitudeDelta;
+    }
+
+    @Override
+    public boolean isCleanUp() {
+        return true;
+    }
+
+    @Override
+    public List<Step> cancel() {
+        return Arrays.asList(
+                new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(), controller));
+    }
+
+    @Override
+    public List<Step> play() {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "RampOffVibratorStep");
+        try {
+            if (VibrationThread.DEBUG) {
+                long latency = SystemClock.uptimeMillis() - startTime;
+                Slog.d(VibrationThread.TAG, "Ramp down the vibrator amplitude, step with "
+                        + latency + "ms latency.");
+            }
+            if (mVibratorCompleteCallbackReceived) {
+                // Vibration completion callback was received by this step, just turn if off
+                // and skip the rest of the steps to ramp down the vibrator amplitude.
+                stopVibrating();
+                return VibrationStepConductor.EMPTY_STEP_LIST;
+            }
+
+            changeAmplitude(mAmplitudeTarget);
+
+            float newAmplitudeTarget = mAmplitudeTarget - mAmplitudeDelta;
+            if (newAmplitudeTarget < VibrationStepConductor.RAMP_OFF_AMPLITUDE_MIN) {
+                // Vibrator amplitude cannot go further down, just turn it off.
+                return Arrays.asList(new TurnOffVibratorStep(
+                        conductor, previousStepVibratorOffTimeout, controller));
+            }
+            return Arrays.asList(new RampOffVibratorStep(
+                    conductor,
+                    startTime + conductor.vibrationSettings.getRampStepDuration(),
+                    newAmplitudeTarget, mAmplitudeDelta, controller,
+                    previousStepVibratorOffTimeout));
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
new file mode 100644
index 0000000..d5c1116
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.VibrationEffect;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.Slog;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a step to turn the vibrator on and change its amplitude.
+ *
+ * <p>This step ignores vibration completion callbacks and control the vibrator on/off state
+ * and amplitude to simulate waveforms represented by a sequence of {@link StepSegment}.
+ */
+final class SetAmplitudeVibratorStep extends AbstractVibratorStep {
+    private long mNextOffTime;
+
+    SetAmplitudeVibratorStep(VibrationStepConductor conductor, long startTime,
+            VibratorController controller, VibrationEffect.Composed effect, int index,
+            long previousStepVibratorOffTimeout) {
+        // This step has a fixed startTime coming from the timings of the waveform it's playing.
+        super(conductor, startTime, controller, effect, index, previousStepVibratorOffTimeout);
+        mNextOffTime = previousStepVibratorOffTimeout;
+    }
+
+    @Override
+    public boolean acceptVibratorCompleteCallback(int vibratorId) {
+        if (controller.getVibratorInfo().getId() == vibratorId) {
+            mVibratorCompleteCallbackReceived = true;
+            mNextOffTime = SystemClock.uptimeMillis();
+        }
+        // Timings are tightly controlled here, so only trigger this step if the vibrator was
+        // supposed to be ON but has completed prematurely, to turn it back on as soon as
+        // possible.
+        return mNextOffTime < startTime && controller.getCurrentAmplitude() > 0;
+    }
+
+    @Override
+    public List<Step> play() {
+        // TODO: consider separating the "on" steps at the start into a separate Step.
+        // TODO: consider instantiating the step with the required amplitude, rather than
+        // needing to dig into the effect.
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "SetAmplitudeVibratorStep");
+        try {
+            long now = SystemClock.uptimeMillis();
+            long latency = now - startTime;
+            if (VibrationThread.DEBUG) {
+                Slog.d(VibrationThread.TAG,
+                        "Running amplitude step with " + latency + "ms latency.");
+            }
+
+            if (mVibratorCompleteCallbackReceived && latency < 0) {
+                // This step was run early because the vibrator turned off prematurely.
+                // Turn it back on and return this same step to run at the exact right time.
+                mNextOffTime = turnVibratorBackOn(/* remainingDuration= */ -latency);
+                return Arrays.asList(new SetAmplitudeVibratorStep(conductor, startTime, controller,
+                        effect, segmentIndex, mNextOffTime));
+            }
+
+            VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
+            if (!(segment instanceof StepSegment)) {
+                Slog.w(VibrationThread.TAG,
+                        "Ignoring wrong segment for a SetAmplitudeVibratorStep: " + segment);
+                return skipToNextSteps(/* segmentsSkipped= */ 1);
+            }
+
+            StepSegment stepSegment = (StepSegment) segment;
+            if (stepSegment.getDuration() == 0) {
+                // Skip waveform entries with zero timing.
+                return skipToNextSteps(/* segmentsSkipped= */ 1);
+            }
+
+            float amplitude = stepSegment.getAmplitude();
+            if (amplitude == 0) {
+                if (previousStepVibratorOffTimeout > now) {
+                    // Amplitude cannot be set to zero, so stop the vibrator.
+                    stopVibrating();
+                    mNextOffTime = now;
+                }
+            } else {
+                if (startTime >= mNextOffTime) {
+                    // Vibrator is OFF. Turn vibrator back on for the duration of another
+                    // cycle before setting the amplitude.
+                    long onDuration = getVibratorOnDuration(effect, segmentIndex);
+                    if (onDuration > 0) {
+                        mVibratorOnResult = startVibrating(onDuration);
+                        mNextOffTime = now + onDuration
+                                + VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
+                    }
+                }
+                changeAmplitude(amplitude);
+            }
+
+            // Use original startTime to avoid propagating latencies to the waveform.
+            long nextStartTime = startTime + segment.getDuration();
+            return nextSteps(nextStartTime, mNextOffTime, /* segmentsPlayed= */ 1);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+
+    private long turnVibratorBackOn(long remainingDuration) {
+        long onDuration = getVibratorOnDuration(effect, segmentIndex);
+        if (onDuration <= 0) {
+            // Vibrator is supposed to go back off when this step starts, so just leave it off.
+            return previousStepVibratorOffTimeout;
+        }
+        onDuration += remainingDuration;
+        float expectedAmplitude = controller.getCurrentAmplitude();
+        mVibratorOnResult = startVibrating(onDuration);
+        if (mVibratorOnResult > 0) {
+            // Set the amplitude back to the value it was supposed to be playing at.
+            changeAmplitude(expectedAmplitude);
+        }
+        return SystemClock.uptimeMillis() + onDuration
+                + VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
+    }
+
+    private long startVibrating(long duration) {
+        if (VibrationThread.DEBUG) {
+            Slog.d(VibrationThread.TAG,
+                    "Turning on vibrator " + controller.getVibratorInfo().getId() + " for "
+                            + duration + "ms");
+        }
+        return controller.on(duration, getVibration().id);
+    }
+
+    /**
+     * Get the duration the vibrator will be on for a waveform, starting at {@code startIndex}
+     * until the next time it's vibrating amplitude is zero or a different type of segment is
+     * found.
+     */
+    private long getVibratorOnDuration(VibrationEffect.Composed effect, int startIndex) {
+        List<VibrationEffectSegment> segments = effect.getSegments();
+        int segmentCount = segments.size();
+        int repeatIndex = effect.getRepeatIndex();
+        int i = startIndex;
+        long timing = 0;
+        while (i < segmentCount) {
+            VibrationEffectSegment segment = segments.get(i);
+            if (!(segment instanceof StepSegment)
+                    || ((StepSegment) segment).getAmplitude() == 0) {
+                break;
+            }
+            timing += segment.getDuration();
+            i++;
+            if (i == segmentCount && repeatIndex >= 0) {
+                i = repeatIndex;
+                // prevent infinite loop
+                repeatIndex = -1;
+            }
+            if (i == startIndex) {
+                // The repeating waveform keeps the vibrator ON all the time. Use a minimum
+                // of 1s duration to prevent short patterns from turning the vibrator ON too
+                // frequently.
+                return Math.max(timing, 1000);
+            }
+        }
+        if (i == segmentCount && effect.getRepeatIndex() < 0) {
+            // Vibration ending at non-zero amplitude, add extra timings to ramp down after
+            // vibration is complete.
+            timing += conductor.vibrationSettings.getRampDownDuration();
+        }
+        return timing;
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
new file mode 100644
index 0000000..080a36c
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.annotation.Nullable;
+import android.hardware.vibrator.IVibratorManager;
+import android.os.CombinedVibration;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.VibrationEffect;
+import android.os.VibratorInfo;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Starts a sync vibration.
+ *
+ * <p>If this step has successfully started playing a vibration on any vibrator, it will always
+ * add a {@link FinishSequentialEffectStep} to the queue, to be played after all vibrators
+ * have finished all their individual steps.
+ *
+ * <p>If this step does not start any vibrator, it will add a {@link StartSequentialEffectStep} if
+ * the sequential effect isn't finished yet.
+ *
+ * <p>TODO: this step actually does several things: multiple HAL calls to sync the vibrators,
+ * as well as dispatching the underlying vibrator instruction calls (which need to be done before
+ * triggering the synced effects). This role/encapsulation could probably be improved to split up
+ * the grouped HAL calls here, as well as to clarify the role of dispatching VibratorSteps between
+ * this class and the controller.
+ */
+final class StartSequentialEffectStep extends Step {
+    public final CombinedVibration.Sequential sequentialEffect;
+    public final int currentIndex;
+
+    private long mVibratorsOnMaxDuration;
+
+    /** Start a sequential effect at the beginning. */
+    StartSequentialEffectStep(VibrationStepConductor conductor,
+            CombinedVibration.Sequential effect) {
+        this(conductor, SystemClock.uptimeMillis() + effect.getDelays().get(0), effect,
+                /* index= */ 0);
+    }
+
+    /** Continue a SequentialEffect from the specified index. */
+    private StartSequentialEffectStep(VibrationStepConductor conductor, long startTime,
+            CombinedVibration.Sequential effect, int index) {
+        super(conductor, startTime);
+        sequentialEffect = effect;
+        currentIndex = index;
+    }
+
+    @Override
+    public long getVibratorOnDuration() {
+        return mVibratorsOnMaxDuration;
+    }
+
+    @Override
+    public List<Step> play() {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "StartSequentialEffectStep");
+        List<Step> nextSteps = new ArrayList<>();
+        mVibratorsOnMaxDuration = -1;
+        try {
+            if (VibrationThread.DEBUG) {
+                Slog.d(VibrationThread.TAG,
+                        "StartSequentialEffectStep for effect #" + currentIndex);
+            }
+            CombinedVibration effect = sequentialEffect.getEffects().get(currentIndex);
+            DeviceEffectMap effectMapping = createEffectToVibratorMapping(effect);
+            if (effectMapping == null) {
+                // Unable to map effects to vibrators, ignore this step.
+                return nextSteps;
+            }
+
+            mVibratorsOnMaxDuration = startVibrating(effectMapping, nextSteps);
+            if (mVibratorsOnMaxDuration > 0) {
+                conductor.vibratorManagerHooks.noteVibratorOn(conductor.getVibration().uid,
+                        mVibratorsOnMaxDuration);
+            }
+        } finally {
+            if (mVibratorsOnMaxDuration >= 0) {
+                // It least one vibrator was started then add a finish step to wait for all
+                // active vibrators to finish their individual steps before going to the next.
+                // Otherwise this step was ignored so just go to the next one.
+                Step nextStep =
+                        mVibratorsOnMaxDuration > 0 ? new FinishSequentialEffectStep(this)
+                                : nextStep();
+                if (nextStep != null) {
+                    nextSteps.add(nextStep);
+                }
+            }
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+        return nextSteps;
+    }
+
+    @Override
+    public List<Step> cancel() {
+        return VibrationStepConductor.EMPTY_STEP_LIST;
+    }
+
+    @Override
+    public void cancelImmediately() {
+    }
+
+    /**
+     * Create the next {@link StartSequentialEffectStep} to play this sequential effect, starting at
+     * the time this method is called, or null if sequence is complete.
+     */
+    @Nullable
+    Step nextStep() {
+        int nextIndex = currentIndex + 1;
+        if (nextIndex >= sequentialEffect.getEffects().size()) {
+            return null;
+        }
+        long nextEffectDelay = sequentialEffect.getDelays().get(nextIndex);
+        long nextStartTime = SystemClock.uptimeMillis() + nextEffectDelay;
+        return new StartSequentialEffectStep(conductor, nextStartTime, sequentialEffect,
+                nextIndex);
+    }
+
+    /** Create a mapping of individual {@link VibrationEffect} to available vibrators. */
+    @Nullable
+    private DeviceEffectMap createEffectToVibratorMapping(
+            CombinedVibration effect) {
+        if (effect instanceof CombinedVibration.Mono) {
+            return new DeviceEffectMap((CombinedVibration.Mono) effect);
+        }
+        if (effect instanceof CombinedVibration.Stereo) {
+            return new DeviceEffectMap((CombinedVibration.Stereo) effect);
+        }
+        return null;
+    }
+
+    /**
+     * Starts playing effects on designated vibrators, in sync.
+     *
+     * @param effectMapping The {@link CombinedVibration} mapped to this device vibrators
+     * @param nextSteps     An output list to accumulate the future {@link Step
+     *                      Steps} created
+     *                      by this method, typically one for each vibrator that has
+     *                      successfully started vibrating on this step.
+     * @return The duration, in millis, of the {@link CombinedVibration}. Repeating
+     * waveforms return {@link Long#MAX_VALUE}. Zero or negative values indicate the vibrators
+     * have ignored all effects.
+     */
+    private long startVibrating(
+            DeviceEffectMap effectMapping, List<Step> nextSteps) {
+        int vibratorCount = effectMapping.size();
+        if (vibratorCount == 0) {
+            // No effect was mapped to any available vibrator.
+            return 0;
+        }
+
+        AbstractVibratorStep[] steps = new AbstractVibratorStep[vibratorCount];
+        long vibrationStartTime = SystemClock.uptimeMillis();
+        for (int i = 0; i < vibratorCount; i++) {
+            steps[i] = conductor.nextVibrateStep(vibrationStartTime,
+                    conductor.getVibrators().get(effectMapping.vibratorIdAt(i)),
+                    effectMapping.effectAt(i),
+                    /* segmentIndex= */ 0, /* vibratorOffTimeout= */ 0);
+        }
+
+        if (steps.length == 1) {
+            // No need to prepare and trigger sync effects on a single vibrator.
+            return startVibrating(steps[0], nextSteps);
+        }
+
+        // This synchronization of vibrators should be executed one at a time, even if we are
+        // vibrating different sets of vibrators in parallel. The manager can only prepareSynced
+        // one set of vibrators at a time.
+        // This property is guaranteed by there only being one thread (VibrationThread) executing
+        // one Step at a time, so there's no need to hold the state lock. Callbacks will be
+        // delivered asynchronously but enqueued until the step processing is finished.
+        boolean hasPrepared = false;
+        boolean hasTriggered = false;
+        long maxDuration = 0;
+        try {
+            hasPrepared = conductor.vibratorManagerHooks.prepareSyncedVibration(
+                    effectMapping.getRequiredSyncCapabilities(),
+                    effectMapping.getVibratorIds());
+
+            for (AbstractVibratorStep step : steps) {
+                long duration = startVibrating(step, nextSteps);
+                if (duration < 0) {
+                    // One vibrator has failed, fail this entire sync attempt.
+                    return maxDuration = -1;
+                }
+                maxDuration = Math.max(maxDuration, duration);
+            }
+
+            // Check if sync was prepared and if any step was accepted by a vibrator,
+            // otherwise there is nothing to trigger here.
+            if (hasPrepared && maxDuration > 0) {
+                hasTriggered = conductor.vibratorManagerHooks.triggerSyncedVibration(
+                        getVibration().id);
+            }
+            return maxDuration;
+        } finally {
+            if (hasPrepared && !hasTriggered) {
+                // Trigger has failed or all steps were ignored by the vibrators.
+                conductor.vibratorManagerHooks.cancelSyncedVibration();
+                nextSteps.clear();
+            } else if (maxDuration < 0) {
+                // Some vibrator failed without being prepared so other vibrators might be
+                // active. Cancel and remove every pending step from output list.
+                for (int i = nextSteps.size() - 1; i >= 0; i--) {
+                    nextSteps.remove(i).cancelImmediately();
+                }
+            }
+        }
+    }
+
+    private long startVibrating(AbstractVibratorStep step, List<Step> nextSteps) {
+        nextSteps.addAll(step.play());
+        long stepDuration = step.getVibratorOnDuration();
+        if (stepDuration < 0) {
+            // Step failed, so return negative duration to propagate failure.
+            return stepDuration;
+        }
+        // Return the longest estimation for the entire effect.
+        return Math.max(stepDuration, step.effect.getDuration());
+    }
+
+    /**
+     * Map a {@link CombinedVibration} to the vibrators available on the device.
+     *
+     * <p>This contains the logic to find the capabilities required from {@link IVibratorManager} to
+     * play all of the effects in sync.
+     */
+    final class DeviceEffectMap {
+        private final SparseArray<VibrationEffect.Composed> mVibratorEffects;
+        private final int[] mVibratorIds;
+        private final long mRequiredSyncCapabilities;
+
+        DeviceEffectMap(CombinedVibration.Mono mono) {
+            SparseArray<VibratorController> vibrators = conductor.getVibrators();
+            mVibratorEffects = new SparseArray<>(vibrators.size());
+            mVibratorIds = new int[vibrators.size()];
+            for (int i = 0; i < vibrators.size(); i++) {
+                int vibratorId = vibrators.keyAt(i);
+                VibratorInfo vibratorInfo = vibrators.valueAt(i).getVibratorInfo();
+                VibrationEffect effect = conductor.deviceEffectAdapter.apply(
+                        mono.getEffect(), vibratorInfo);
+                if (effect instanceof VibrationEffect.Composed) {
+                    mVibratorEffects.put(vibratorId, (VibrationEffect.Composed) effect);
+                    mVibratorIds[i] = vibratorId;
+                }
+            }
+            mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
+        }
+
+        DeviceEffectMap(CombinedVibration.Stereo stereo) {
+            SparseArray<VibratorController> vibrators = conductor.getVibrators();
+            SparseArray<VibrationEffect> stereoEffects = stereo.getEffects();
+            mVibratorEffects = new SparseArray<>();
+            for (int i = 0; i < stereoEffects.size(); i++) {
+                int vibratorId = stereoEffects.keyAt(i);
+                if (vibrators.contains(vibratorId)) {
+                    VibratorInfo vibratorInfo = vibrators.valueAt(i).getVibratorInfo();
+                    VibrationEffect effect = conductor.deviceEffectAdapter.apply(
+                            stereoEffects.valueAt(i), vibratorInfo);
+                    if (effect instanceof VibrationEffect.Composed) {
+                        mVibratorEffects.put(vibratorId, (VibrationEffect.Composed) effect);
+                    }
+                }
+            }
+            mVibratorIds = new int[mVibratorEffects.size()];
+            for (int i = 0; i < mVibratorEffects.size(); i++) {
+                mVibratorIds[i] = mVibratorEffects.keyAt(i);
+            }
+            mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
+        }
+
+        /**
+         * Return the number of vibrators mapped to play the {@link CombinedVibration} on this
+         * device.
+         */
+        public int size() {
+            return mVibratorIds.length;
+        }
+
+        /**
+         * Return all capabilities required to play the {@link CombinedVibration} in
+         * between calls to {@link IVibratorManager#prepareSynced} and
+         * {@link IVibratorManager#triggerSynced}.
+         */
+        public long getRequiredSyncCapabilities() {
+            return mRequiredSyncCapabilities;
+        }
+
+        /** Return all vibrator ids mapped to play the {@link CombinedVibration}. */
+        public int[] getVibratorIds() {
+            return mVibratorIds;
+        }
+
+        /** Return the id of the vibrator at given index. */
+        public int vibratorIdAt(int index) {
+            return mVibratorEffects.keyAt(index);
+        }
+
+        /** Return the {@link VibrationEffect} at given index. */
+        public VibrationEffect.Composed effectAt(int index) {
+            return mVibratorEffects.valueAt(index);
+        }
+
+        /**
+         * Return all capabilities required from the {@link IVibratorManager} to prepare and
+         * trigger all given effects in sync.
+         *
+         * @return {@link IVibratorManager#CAP_SYNC} together with all required
+         * IVibratorManager.CAP_PREPARE_* and IVibratorManager.CAP_MIXED_TRIGGER_* capabilities.
+         */
+        private long calculateRequiredSyncCapabilities(
+                SparseArray<VibrationEffect.Composed> effects) {
+            long prepareCap = 0;
+            for (int i = 0; i < effects.size(); i++) {
+                VibrationEffectSegment firstSegment = effects.valueAt(i).getSegments().get(0);
+                if (firstSegment instanceof StepSegment) {
+                    prepareCap |= IVibratorManager.CAP_PREPARE_ON;
+                } else if (firstSegment instanceof PrebakedSegment) {
+                    prepareCap |= IVibratorManager.CAP_PREPARE_PERFORM;
+                } else if (firstSegment instanceof PrimitiveSegment) {
+                    prepareCap |= IVibratorManager.CAP_PREPARE_COMPOSE;
+                }
+            }
+            int triggerCap = 0;
+            if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_ON)) {
+                triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_ON;
+            }
+            if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_PERFORM)) {
+                triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_PERFORM;
+            }
+            if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_COMPOSE)) {
+                triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_COMPOSE;
+            }
+            return IVibratorManager.CAP_SYNC | prepareCap | triggerCap;
+        }
+
+        /**
+         * Return true if {@code prepareCapabilities} contains this {@code capability} mixed with
+         * different ones, requiring a mixed trigger capability from the vibrator manager for
+         * syncing all effects.
+         */
+        private boolean requireMixedTriggerCapability(long prepareCapabilities, long capability) {
+            return (prepareCapabilities & capability) != 0
+                    && (prepareCapabilities & ~capability) != 0;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/Step.java b/services/core/java/com/android/server/vibrator/Step.java
new file mode 100644
index 0000000..042e8a0
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/Step.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.annotation.NonNull;
+import android.os.CombinedVibration;
+import android.os.SystemClock;
+import android.os.VibrationEffect;
+
+import java.util.List;
+
+/**
+ * Represent a single step for playing a vibration.
+ *
+ * <p>Every step has a start time, which can be used to apply delays between steps while
+ * executing them in sequence.
+ */
+abstract class Step implements Comparable<Step> {
+    public final VibrationStepConductor conductor;
+    public final long startTime;
+
+    Step(VibrationStepConductor conductor, long startTime) {
+        this.conductor = conductor;
+        this.startTime = startTime;
+    }
+
+    protected Vibration getVibration() {
+        return conductor.getVibration();
+    }
+
+    /**
+     * Returns true if this step is a clean up step and not part of a {@link VibrationEffect} or
+     * {@link CombinedVibration}.
+     */
+    public boolean isCleanUp() {
+        return false;
+    }
+
+    /** Play this step, returning a (possibly empty) list of next steps. */
+    @NonNull
+    public abstract List<Step> play();
+
+    /**
+     * Cancel this pending step and return a (possibly empty) list of clean-up steps that should
+     * be played to gracefully cancel this step.
+     */
+    @NonNull
+    public abstract List<Step> cancel();
+
+    /** Cancel this pending step immediately, skipping any clean-up. */
+    public abstract void cancelImmediately();
+
+    /**
+     * Return the duration the vibrator was turned on when this step was played.
+     *
+     * @return A positive duration that the vibrator was turned on for by this step;
+     * Zero if the segment is not supported, the step was not played yet or vibrator was never
+     * turned on by this step; A negative value if the vibrator call has failed.
+     */
+    public long getVibratorOnDuration() {
+        return 0;
+    }
+
+    /**
+     * Return true to run this step right after a vibrator has notified vibration completed,
+     * used to resume steps waiting on vibrator callbacks with a timeout.
+     */
+    public boolean acceptVibratorCompleteCallback(int vibratorId) {
+        return false;
+    }
+
+    /**
+     * Returns the time in millis to wait before playing this step. This is performed
+     * while holding the queue lock, so should not rely on potentially slow operations.
+     */
+    public long calculateWaitTime() {
+        if (startTime == Long.MAX_VALUE) {
+            // This step don't have a predefined start time, it's just marked to be executed
+            // after all other steps have finished.
+            return 0;
+        }
+        return Math.max(0, startTime - SystemClock.uptimeMillis());
+    }
+
+    @Override
+    public int compareTo(Step o) {
+        return Long.compare(startTime, o.startTime);
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java b/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java
new file mode 100644
index 0000000..297ef56
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.SystemClock;
+import android.os.Trace;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a step to turn the vibrator off.
+ *
+ * <p>This runs after a timeout on the expected time the vibrator should have finished playing,
+ * and can be brought forward by vibrator complete callbacks. The step shouldn't be skipped, even
+ * if the vibrator-complete callback was received, as some implementations still rely on the
+ * "off" call to actually stop.
+ */
+final class TurnOffVibratorStep extends AbstractVibratorStep {
+
+    TurnOffVibratorStep(VibrationStepConductor conductor, long startTime,
+            VibratorController controller) {
+        super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1, startTime);
+    }
+
+    @Override
+    public boolean isCleanUp() {
+        return true;
+    }
+
+    @Override
+    public List<Step> cancel() {
+        return Arrays.asList(
+                new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(), controller));
+    }
+
+    @Override
+    public void cancelImmediately() {
+        stopVibrating();
+    }
+
+    @Override
+    public List<Step> play() {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "TurnOffVibratorStep");
+        try {
+            stopVibrating();
+            return VibrationStepConductor.EMPTY_STEP_LIST;
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
new file mode 100644
index 0000000..3667631
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.annotation.Nullable;
+import android.os.Build;
+import android.os.CombinedVibration;
+import android.os.VibrationEffect;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.PriorityQueue;
+import java.util.Queue;
+
+/**
+ * Creates and manages a queue of steps for performing a VibrationEffect, as well as coordinating
+ * dispatch of callbacks.
+ *
+ * <p>In general, methods in this class are intended to be called only by a single instance of
+ * VibrationThread. The only thread-safe methods for calling from other threads are the "notify"
+ * methods (which should never be used from the VibrationThread thread).
+ */
+final class VibrationStepConductor {
+    private static final boolean DEBUG = VibrationThread.DEBUG;
+    private static final String TAG = VibrationThread.TAG;
+
+    /**
+     * Extra timeout added to the end of each vibration step to ensure it finishes even when
+     * vibrator callbacks are lost.
+     */
+    static final long CALLBACKS_EXTRA_TIMEOUT = 1_000;
+    /** Threshold to prevent the ramp off steps from trying to set extremely low amplitudes. */
+    static final float RAMP_OFF_AMPLITUDE_MIN = 1e-3f;
+    static final List<Step> EMPTY_STEP_LIST = new ArrayList<>();
+
+    private final Object mLock = new Object();
+
+    // Used within steps.
+    public final VibrationSettings vibrationSettings;
+    public final DeviceVibrationEffectAdapter deviceEffectAdapter;
+    public final VibrationThread.VibratorManagerHooks vibratorManagerHooks;
+
+    private final Vibration mVibration;
+    private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
+
+    private final PriorityQueue<Step> mNextSteps = new PriorityQueue<>();
+    private final Queue<Step> mPendingOnVibratorCompleteSteps = new LinkedList<>();
+    @GuardedBy("mLock")
+    private Queue<Integer> mCompletionNotifiedVibrators = new LinkedList<>();
+
+    private int mPendingVibrateSteps;
+    private int mRemainingStartSequentialEffectSteps;
+    private int mSuccessfulVibratorOnSteps;
+
+    VibrationStepConductor(Vibration vib, VibrationSettings vibrationSettings,
+            DeviceVibrationEffectAdapter effectAdapter,
+            SparseArray<VibratorController> availableVibrators,
+            VibrationThread.VibratorManagerHooks vibratorManagerHooks) {
+        this.mVibration = vib;
+        this.vibrationSettings = vibrationSettings;
+        this.deviceEffectAdapter = effectAdapter;
+        this.vibratorManagerHooks = vibratorManagerHooks;
+
+        CombinedVibration effect = vib.getEffect();
+        for (int i = 0; i < availableVibrators.size(); i++) {
+            if (effect.hasVibrator(availableVibrators.keyAt(i))) {
+                mVibrators.put(availableVibrators.keyAt(i), availableVibrators.valueAt(i));
+            }
+        }
+    }
+
+    @Nullable
+    AbstractVibratorStep nextVibrateStep(long startTime, VibratorController controller,
+            VibrationEffect.Composed effect, int segmentIndex,
+            long previousStepVibratorOffTimeout) {
+        if (Build.IS_DEBUGGABLE) {
+            expectIsVibrationThread(true);
+        }
+        if (segmentIndex >= effect.getSegments().size()) {
+            segmentIndex = effect.getRepeatIndex();
+        }
+        if (segmentIndex < 0) {
+            // No more segments to play, last step is to complete the vibration on this vibrator.
+            return new CompleteEffectVibratorStep(this, startTime, /* cancelled= */ false,
+                    controller, previousStepVibratorOffTimeout);
+        }
+
+        VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
+        if (segment instanceof PrebakedSegment) {
+            return new PerformPrebakedVibratorStep(this, startTime, controller, effect,
+                    segmentIndex, previousStepVibratorOffTimeout);
+        }
+        if (segment instanceof PrimitiveSegment) {
+            return new ComposePrimitivesVibratorStep(this, startTime, controller, effect,
+                    segmentIndex, previousStepVibratorOffTimeout);
+        }
+        if (segment instanceof RampSegment) {
+            return new ComposePwleVibratorStep(this, startTime, controller, effect, segmentIndex,
+                    previousStepVibratorOffTimeout);
+        }
+        return new SetAmplitudeVibratorStep(this, startTime, controller, effect, segmentIndex,
+                previousStepVibratorOffTimeout);
+    }
+
+    /** Called when this conductor is going to be started running by the VibrationThread. */
+    public void prepareToStart() {
+        if (Build.IS_DEBUGGABLE) {
+            expectIsVibrationThread(true);
+        }
+        CombinedVibration.Sequential sequentialEffect = toSequential(mVibration.getEffect());
+        mPendingVibrateSteps++;
+        // This count is decremented at the completion of the step, so we don't subtract one.
+        mRemainingStartSequentialEffectSteps = sequentialEffect.getEffects().size();
+        mNextSteps.offer(new StartSequentialEffectStep(this, sequentialEffect));
+    }
+
+    public Vibration getVibration() {
+        // No thread assertion: immutable
+        return mVibration;
+    }
+
+    SparseArray<VibratorController> getVibrators() {
+        // No thread assertion: immutable
+        return mVibrators;
+    }
+
+    public boolean isFinished() {
+        if (Build.IS_DEBUGGABLE) {
+            expectIsVibrationThread(true);
+        }
+        // No need to check for vibration complete callbacks - if there were any, they would
+        // have no steps to notify anyway.
+        return mPendingOnVibratorCompleteSteps.isEmpty() && mNextSteps.isEmpty();
+    }
+
+    /**
+     * Calculate the {@link Vibration.Status} based on the current queue state and the expected
+     * number of {@link StartSequentialEffectStep} to be played.
+     */
+    public Vibration.Status calculateVibrationStatus() {
+        if (Build.IS_DEBUGGABLE) {
+            expectIsVibrationThread(true);
+        }
+
+        if (mPendingVibrateSteps > 0
+                || mRemainingStartSequentialEffectSteps > 0) {
+            return Vibration.Status.RUNNING;
+        }
+        // No pending steps, and something happened.
+        if (mSuccessfulVibratorOnSteps > 0) {
+            return Vibration.Status.FINISHED;
+        }
+        // If no step was able to turn the vibrator ON successfully.
+        return Vibration.Status.IGNORED_UNSUPPORTED;
+    }
+
+    /**
+     * Blocks until the next step is due to run. The wait here may be interrupted by calling
+     * {@link #notifyWakeUp} or other "notify" methods.
+     *
+     * <p>This method returns false if the next step is ready to run now. If the method returns
+     * true, then some waiting was done, but may have been interrupted by a wakeUp.
+     *
+     * @return true if the method waited at all, or false if a step is ready to run now.
+     */
+    public boolean waitUntilNextStepIsDue() {
+        if (Build.IS_DEBUGGABLE) {
+            expectIsVibrationThread(true);
+        }
+        // It's necessary to re-process callbacks if they come in after acquiring the lock to
+        // start waiting, but we don't want to hold the lock while processing them.
+        // The loop goes until there are no pending callbacks to process.
+        while (true) {
+            // TODO: cancellation checking could also be integrated here, instead of outside in
+            // VibrationThread.
+            processVibratorCompleteCallbacks();
+            if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
+                // Steps resumed by vibrator complete callback should be played right away.
+                return false;
+            }
+            Step nextStep = mNextSteps.peek();
+            if (nextStep == null) {
+                return false;
+            }
+            long waitMillis = nextStep.calculateWaitTime();
+            if (waitMillis <= 0) {
+                return false;
+            }
+            synchronized (mLock) {
+                // Double check for missed wake-ups before sleeping.
+                if (!mCompletionNotifiedVibrators.isEmpty()) {
+                    continue;  // Start again: processVibratorCompleteCallbacks will consume it.
+                }
+                try {
+                    mLock.wait(waitMillis);
+                } catch (InterruptedException e) {
+                }
+                return true;
+            }
+        }
+    }
+
+    /**
+     * Play and remove the step at the top of this queue, and also adds the next steps generated
+     * to be played next.
+     */
+    public void runNextStep() {
+        if (Build.IS_DEBUGGABLE) {
+            expectIsVibrationThread(true);
+        }
+        // In theory a completion callback could have come in between the wait finishing and
+        // this method starting, but that only means the step is due now anyway, so it's reasonable
+        // to run it before processing callbacks as the window is tiny.
+        Step nextStep = pollNext();
+        if (nextStep != null) {
+            List<Step> nextSteps = nextStep.play();
+            if (nextStep.getVibratorOnDuration() > 0) {
+                mSuccessfulVibratorOnSteps++;
+            }
+            if (nextStep instanceof StartSequentialEffectStep) {
+                mRemainingStartSequentialEffectSteps--;
+            }
+            if (!nextStep.isCleanUp()) {
+                mPendingVibrateSteps--;
+            }
+            for (int i = 0; i < nextSteps.size(); i++) {
+                mPendingVibrateSteps += nextSteps.get(i).isCleanUp() ? 0 : 1;
+            }
+            mNextSteps.addAll(nextSteps);
+        }
+    }
+
+    /**
+     * Wake up the execution thread, which may be waiting until the next step is due.
+     * The caller is responsible for diverting VibrationThread execution.
+     *
+     * <p>At the moment this is used after the signal is set that a cancellation needs to be
+     * processed. The actual cancellation will be invoked from the VibrationThread.
+     */
+    public void notifyWakeUp() {
+        if (Build.IS_DEBUGGABLE) {
+            expectIsVibrationThread(false);
+        }
+
+        synchronized (mLock) {
+            mLock.notify();
+        }
+    }
+
+    /**
+     * Notify the conductor that a vibrator has completed its work.
+     *
+     * <p>This is a lightweight method intended to be called directly via native callbacks.
+     * The state update is recorded for processing on the main execution thread (VibrationThread).
+     */
+    public void notifyVibratorComplete(int vibratorId) {
+        if (Build.IS_DEBUGGABLE) {
+            expectIsVibrationThread(false);
+        }
+
+        if (DEBUG) {
+            Slog.d(TAG, "Vibration complete reported by vibrator " + vibratorId);
+        }
+
+        synchronized (mLock) {
+            mCompletionNotifiedVibrators.offer(vibratorId);
+            mLock.notify();
+        }
+    }
+
+    /**
+     * Notify that a VibratorManager sync operation has completed.
+     *
+     * <p>This is a lightweight method intended to be called directly via native callbacks.
+     * The state update is recorded for processing on the main execution thread
+     * (VibrationThread).
+     */
+    public void notifySyncedVibrationComplete() {
+        if (Build.IS_DEBUGGABLE) {
+            expectIsVibrationThread(false);
+        }
+
+        if (DEBUG) {
+            Slog.d(TAG, "Synced vibration complete reported by vibrator manager");
+        }
+
+        synchronized (mLock) {
+            for (int i = 0; i < mVibrators.size(); i++) {
+                mCompletionNotifiedVibrators.offer(mVibrators.keyAt(i));
+            }
+            mLock.notify();
+        }
+    }
+
+    /**
+     * Cancel the current queue, replacing all remaining steps with respective clean-up steps.
+     *
+     * <p>This will remove all steps and replace them with respective
+     * {@link Step#cancel()}.
+     */
+    public void cancel() {
+        if (Build.IS_DEBUGGABLE) {
+            expectIsVibrationThread(true);
+        }
+
+        // Vibrator callbacks should wait until all steps from the queue are properly cancelled
+        // and clean up steps are added back to the queue, so they can handle the callback.
+        List<Step> cleanUpSteps = new ArrayList<>();
+        Step step;
+        while ((step = pollNext()) != null) {
+            cleanUpSteps.addAll(step.cancel());
+        }
+        // All steps generated by Step.cancel() should be clean-up steps.
+        mPendingVibrateSteps = 0;
+        mNextSteps.addAll(cleanUpSteps);
+    }
+
+    /**
+     * Cancel the current queue immediately, clearing all remaining steps and skipping clean-up.
+     *
+     * <p>This will remove and trigger {@link Step#cancelImmediately()} in all steps, in order.
+     */
+    public void cancelImmediately() {
+        if (Build.IS_DEBUGGABLE) {
+            expectIsVibrationThread(true);
+        }
+
+        Step step;
+        while ((step = pollNext()) != null) {
+            step.cancelImmediately();
+        }
+        mPendingVibrateSteps = 0;
+    }
+
+    @Nullable
+    private Step pollNext() {
+        if (Build.IS_DEBUGGABLE) {
+            expectIsVibrationThread(true);
+        }
+
+        // Prioritize the steps resumed by a vibrator complete callback, irrespective of their
+        // "next run time".
+        if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
+            return mPendingOnVibratorCompleteSteps.poll();
+        }
+        return mNextSteps.poll();
+    }
+
+    /**
+     * Process any notified vibrator completions.
+     *
+     * <p>This assumes only one of the next steps is waiting on this given vibrator, so the
+     * first step found will be resumed by this method, in no particular order.
+     */
+    private void processVibratorCompleteCallbacks() {
+        if (Build.IS_DEBUGGABLE) {
+            expectIsVibrationThread(true);
+        }
+
+        Queue<Integer> vibratorsToProcess;
+        // Swap out the queue of completions to process.
+        synchronized (mLock) {
+            if (mCompletionNotifiedVibrators.isEmpty()) {
+                return;  // Nothing to do.
+            }
+
+            vibratorsToProcess = mCompletionNotifiedVibrators;
+            mCompletionNotifiedVibrators = new LinkedList<>();
+        }
+
+        while (!vibratorsToProcess.isEmpty()) {
+            int vibratorId = vibratorsToProcess.poll();
+            Iterator<Step> it = mNextSteps.iterator();
+            while (it.hasNext()) {
+                Step step = it.next();
+                if (step.acceptVibratorCompleteCallback(vibratorId)) {
+                    it.remove();
+                    mPendingOnVibratorCompleteSteps.offer(step);
+                    break;
+                }
+            }
+        }
+    }
+
+    private static CombinedVibration.Sequential toSequential(CombinedVibration effect) {
+        if (effect instanceof CombinedVibration.Sequential) {
+            return (CombinedVibration.Sequential) effect;
+        }
+        return (CombinedVibration.Sequential) CombinedVibration.startSequential()
+                .addNext(effect)
+                .combine();
+    }
+
+    /**
+     * This check is used for debugging and documentation to indicate the thread that's expected
+     * to invoke a given public method on this class. Most methods are only invoked by
+     * VibrationThread, which is where all the steps and HAL calls should be made. Other threads
+     * should only signal to the execution flow being run by VibrationThread.
+     */
+    private static void expectIsVibrationThread(boolean isVibrationThread) {
+        if ((Thread.currentThread() instanceof VibrationThread) != isVibrationThread) {
+            Slog.wtfStack("VibrationStepConductor",
+                    "Thread caller assertion failed, expected isVibrationThread="
+                            + isVibrationThread);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index 1f1f40b..3fef7f2 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -16,59 +16,21 @@
 
 package com.android.server.vibrator;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.hardware.vibrator.IVibratorManager;
-import android.os.CombinedVibration;
 import android.os.IBinder;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.Trace;
-import android.os.VibrationEffect;
-import android.os.VibratorInfo;
 import android.os.WorkSource;
-import android.os.vibrator.PrebakedSegment;
-import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.RampSegment;
-import android.os.vibrator.StepSegment;
-import android.os.vibrator.VibrationEffectSegment;
 import android.util.Slog;
 import android.util.SparseArray;
 
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.util.FrameworkStatsLog;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
 import java.util.NoSuchElementException;
-import java.util.PriorityQueue;
-import java.util.Queue;
 
 /** Plays a {@link Vibration} in dedicated thread. */
 final class VibrationThread extends Thread implements IBinder.DeathRecipient {
-    private static final String TAG = "VibrationThread";
-    private static final boolean DEBUG = false;
-
-    /**
-     * Extra timeout added to the end of each vibration step to ensure it finishes even when
-     * vibrator callbacks are lost.
-     */
-    private static final long CALLBACKS_EXTRA_TIMEOUT = 1_000;
-
-    /** Threshold to prevent the ramp off steps from trying to set extremely low amplitudes. */
-    private static final float RAMP_OFF_AMPLITUDE_MIN = 1e-3f;
-
-    /** Fixed large duration used to note repeating vibrations to {@link IBatteryStats}. */
-    private static final long BATTERY_STATS_REPEATING_VIBRATION_DURATION = 5_000;
-
-    private static final List<Step> EMPTY_STEP_LIST = new ArrayList<>();
+    static final String TAG = "VibrationThread";
+    static final boolean DEBUG = false;
 
     /** Calls into VibratorManager functionality needed for playing a {@link Vibration}. */
     interface VibratorManagerHooks {
@@ -94,6 +56,15 @@
         void cancelSyncedVibration();
 
         /**
+         * Record that a vibrator was turned on, and may remain on for the specified duration,
+         * on behalf of the given uid.
+         */
+        void noteVibratorOn(int uid, long duration);
+
+        /** Record that a vibrator was turned off, on behalf of the given uid. */
+        void noteVibratorOff(int uid);
+
+        /**
          * Tell the manager that the currently active vibration has completed its vibration, from
          * the perspective of the Effect. However, the VibrationThread may still be continuing with
          * cleanup tasks, and should not be given new work until {@link #onVibrationThreadReleased}
@@ -108,16 +79,10 @@
         void onVibrationThreadReleased();
     }
 
-    private final Object mLock = new Object();
-    private final WorkSource mWorkSource;
     private final PowerManager.WakeLock mWakeLock;
-    private final IBatteryStats mBatteryStatsService;
-    private final VibrationSettings mVibrationSettings;
-    private final DeviceVibrationEffectAdapter mDeviceEffectAdapter;
-    private final Vibration mVibration;
-    private final VibratorManagerHooks mVibratorManagerHooks;
-    private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
-    private final StepQueue mStepQueue = new StepQueue();
+    private final VibrationThread.VibratorManagerHooks mVibratorManagerHooks;
+
+    private final VibrationStepConductor mStepConductor;
 
     private volatile boolean mStop;
     private volatile boolean mForceStop;
@@ -127,30 +92,15 @@
     VibrationThread(Vibration vib, VibrationSettings vibrationSettings,
             DeviceVibrationEffectAdapter effectAdapter,
             SparseArray<VibratorController> availableVibrators, PowerManager.WakeLock wakeLock,
-            IBatteryStats batteryStatsService, VibratorManagerHooks vibratorManagerHooks) {
-        mVibration = vib;
-        mVibrationSettings = vibrationSettings;
-        mDeviceEffectAdapter = effectAdapter;
+            VibratorManagerHooks vibratorManagerHooks) {
         mVibratorManagerHooks = vibratorManagerHooks;
-        mWorkSource = new WorkSource(mVibration.uid);
         mWakeLock = wakeLock;
-        mBatteryStatsService = batteryStatsService;
-
-        CombinedVibration effect = vib.getEffect();
-        for (int i = 0; i < availableVibrators.size(); i++) {
-            if (effect.hasVibrator(availableVibrators.keyAt(i))) {
-                mVibrators.put(availableVibrators.keyAt(i), availableVibrators.valueAt(i));
-            }
-        }
+        mStepConductor = new VibrationStepConductor(vib, vibrationSettings, effectAdapter,
+                availableVibrators, vibratorManagerHooks);
     }
 
     Vibration getVibration() {
-        return mVibration;
-    }
-
-    @VisibleForTesting
-    SparseArray<VibratorController> getVibrators() {
-        return mVibrators;
+        return mStepConductor.getVibration();
     }
 
     @Override
@@ -179,12 +129,14 @@
 
     /** Runs the VibrationThread ensuring that the wake lock is acquired and released. */
     private void runWithWakeLock() {
-        mWakeLock.setWorkSource(mWorkSource);
+        WorkSource workSource = new WorkSource(mStepConductor.getVibration().uid);
+        mWakeLock.setWorkSource(workSource);
         mWakeLock.acquire();
         try {
             runWithWakeLockAndDeathLink();
         } finally {
             mWakeLock.release();
+            mWakeLock.setWorkSource(null);
         }
     }
 
@@ -193,8 +145,9 @@
      * Called from within runWithWakeLock.
      */
     private void runWithWakeLockAndDeathLink() {
+        IBinder vibrationBinderToken = mStepConductor.getVibration().token;
         try {
-            mVibration.token.linkToDeath(this, 0);
+            vibrationBinderToken.linkToDeath(this, 0);
         } catch (RemoteException e) {
             Slog.e(TAG, "Error linking vibration to token death", e);
             clientVibrationCompleteIfNotAlready(Vibration.Status.IGNORED_ERROR_TOKEN);
@@ -206,7 +159,7 @@
             playVibration();
         } finally {
             try {
-                mVibration.token.unlinkToDeath(this, 0);
+                vibrationBinderToken.unlinkToDeath(this, 0);
             } catch (NoSuchElementException e) {
                 Slog.wtf(TAG, "Failed to unlink token", e);
             }
@@ -220,12 +173,10 @@
             return;
         }
         mStop = true;
-        synchronized (mLock) {
-            if (DEBUG) {
-                Slog.d(TAG, "Vibration cancelled");
-            }
-            mLock.notify();
+        if (DEBUG) {
+            Slog.d(TAG, "Vibration cancelled");
         }
+        mStepConductor.notifyWakeUp();
     }
 
     /** Cancel current vibration and shuts off the vibrators immediately. */
@@ -234,37 +185,21 @@
             // Already forced the thread to stop, wait for it to finish.
             return;
         }
-        mStop = mForceStop = true;
-        synchronized (mLock) {
-            if (DEBUG) {
-                Slog.d(TAG, "Vibration cancelled immediately");
-            }
-            mLock.notify();
+        if (DEBUG) {
+            Slog.d(TAG, "Vibration cancelled immediately");
         }
+        mStop = mForceStop = true;
+        mStepConductor.notifyWakeUp();
     }
 
     /** Notify current vibration that a synced step has completed. */
     public void syncedVibrationComplete() {
-        synchronized (mLock) {
-            if (DEBUG) {
-                Slog.d(TAG, "Synced vibration complete reported by vibrator manager");
-            }
-            for (int i = 0; i < mVibrators.size(); i++) {
-                mStepQueue.notifyVibratorComplete(mVibrators.keyAt(i));
-            }
-            mLock.notify();
-        }
+        mStepConductor.notifySyncedVibrationComplete();
     }
 
     /** Notify current vibration that a step has completed on given vibrator. */
     public void vibratorComplete(int vibratorId) {
-        synchronized (mLock) {
-            if (DEBUG) {
-                Slog.d(TAG, "Vibration complete reported by vibrator " + vibratorId);
-            }
-            mStepQueue.notifyVibratorComplete(vibratorId);
-            mLock.notify();
-        }
+        mStepConductor.notifyVibratorComplete(vibratorId);
     }
 
     // Indicate that the vibration is complete. This can be called multiple times only for
@@ -273,1387 +208,51 @@
     private void clientVibrationCompleteIfNotAlready(Vibration.Status completedStatus) {
         if (!mCalledVibrationCompleteCallback) {
             mCalledVibrationCompleteCallback = true;
-            mVibratorManagerHooks.onVibrationCompleted(mVibration.id, completedStatus);
+            mVibratorManagerHooks.onVibrationCompleted(
+                    mStepConductor.getVibration().id, completedStatus);
         }
     }
 
     private void playVibration() {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration");
         try {
-            CombinedVibration.Sequential sequentialEffect = toSequential(mVibration.getEffect());
-            final int sequentialEffectSize = sequentialEffect.getEffects().size();
-            mStepQueue.initializeForEffect(sequentialEffect);
+            mStepConductor.prepareToStart();
 
-            while (!mStepQueue.isFinished()) {
-                long waitMillisBeforeNextStep;
-                synchronized (mLock) {
-                    waitMillisBeforeNextStep = mStepQueue.getWaitMillisBeforeNextStep();
-                    if (waitMillisBeforeNextStep > 0) {
-                        try {
-                            mLock.wait(waitMillisBeforeNextStep);
-                        } catch (InterruptedException e) {
-                        }
-                    }
-                }
-                // Only run the next vibration step if we didn't have to wait in this loop.
-                // If we waited then the queue may have changed, so loop again to re-evaluate
-                // the scheduling of the queue top element.
-                if (waitMillisBeforeNextStep <= 0) {
+            while (!mStepConductor.isFinished()) {
+                // Skip wait and next step if mForceStop already happened.
+                boolean waited = mForceStop || mStepConductor.waitUntilNextStepIsDue();
+                // If we waited, don't run the next step, but instead re-evaluate cancellation
+                // status
+                if (!waited) {
                     if (DEBUG) {
                         Slog.d(TAG, "Play vibration consuming next step...");
                     }
                     // Run the step without holding the main lock, to avoid HAL interactions from
                     // blocking the thread.
-                    mStepQueue.runNextStep();
+                    mStepConductor.runNextStep();
                 }
+
+                if (mForceStop) {
+                    // Cancel every step and stop playing them right away, even clean-up steps.
+                    mStepConductor.cancelImmediately();
+                    clientVibrationCompleteIfNotAlready(Vibration.Status.CANCELLED);
+                    break;
+                }
+
                 Vibration.Status status = mStop ? Vibration.Status.CANCELLED
-                        : mStepQueue.calculateVibrationStatus(sequentialEffectSize);
+                        : mStepConductor.calculateVibrationStatus();
+                // This block can only run once due to mCalledVibrationCompleteCallback.
                 if (status != Vibration.Status.RUNNING && !mCalledVibrationCompleteCallback) {
                     // First time vibration stopped running, start clean-up tasks and notify
                     // callback immediately.
                     clientVibrationCompleteIfNotAlready(status);
                     if (status == Vibration.Status.CANCELLED) {
-                        mStepQueue.cancel();
+                        mStepConductor.cancel();
                     }
                 }
-                if (mForceStop) {
-                    // Cancel every step and stop playing them right away, even clean-up steps.
-                    mStepQueue.cancelImmediately();
-                    clientVibrationCompleteIfNotAlready(Vibration.Status.CANCELLED);
-                    break;
-                }
             }
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
-
-    private void noteVibratorOn(long duration) {
-        try {
-            if (duration <= 0) {
-                return;
-            }
-            if (duration == Long.MAX_VALUE) {
-                // Repeating duration has started. Report a fixed duration here, noteVibratorOff
-                // should be called when this is cancelled.
-                duration = BATTERY_STATS_REPEATING_VIBRATION_DURATION;
-            }
-            mBatteryStatsService.noteVibratorOn(mVibration.uid, duration);
-            FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
-                    mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON,
-                    duration);
-        } catch (RemoteException e) {
-        }
-    }
-
-    private void noteVibratorOff() {
-        try {
-            mBatteryStatsService.noteVibratorOff(mVibration.uid);
-            FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
-                    mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF,
-                    /* duration= */ 0);
-        } catch (RemoteException e) {
-        }
-    }
-
-    @Nullable
-    private SingleVibratorStep nextVibrateStep(long startTime, VibratorController controller,
-            VibrationEffect.Composed effect, int segmentIndex, long vibratorOffTimeout) {
-        if (segmentIndex >= effect.getSegments().size()) {
-            segmentIndex = effect.getRepeatIndex();
-        }
-        if (segmentIndex < 0) {
-            // No more segments to play, last step is to complete the vibration on this vibrator.
-            return new EffectCompleteStep(startTime, /* cancelled= */ false, controller,
-                    vibratorOffTimeout);
-        }
-
-        VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
-        if (segment instanceof PrebakedSegment) {
-            return new PerformStep(startTime, controller, effect, segmentIndex, vibratorOffTimeout);
-        }
-        if (segment instanceof PrimitiveSegment) {
-            return new ComposePrimitivesStep(startTime, controller, effect, segmentIndex,
-                    vibratorOffTimeout);
-        }
-        if (segment instanceof RampSegment) {
-            return new ComposePwleStep(startTime, controller, effect, segmentIndex,
-                    vibratorOffTimeout);
-        }
-        return new AmplitudeStep(startTime, controller, effect, segmentIndex, vibratorOffTimeout);
-    }
-
-    private static CombinedVibration.Sequential toSequential(CombinedVibration effect) {
-        if (effect instanceof CombinedVibration.Sequential) {
-            return (CombinedVibration.Sequential) effect;
-        }
-        return (CombinedVibration.Sequential) CombinedVibration.startSequential()
-                .addNext(effect)
-                .combine();
-    }
-
-    /** Queue for {@link Step Steps}, sorted by their start time. */
-    private final class StepQueue {
-        @GuardedBy("mLock")
-        private final PriorityQueue<Step> mNextSteps = new PriorityQueue<>();
-        @GuardedBy("mLock")
-        private final Queue<Step> mPendingOnVibratorCompleteSteps = new LinkedList<>();
-        @GuardedBy("mLock")
-        private final Queue<Integer> mCompletionNotifiedVibrators = new LinkedList<>();
-
-        @GuardedBy("mLock")
-        private int mPendingVibrateSteps;
-        @GuardedBy("mLock")
-        private int mConsumedStartVibrateSteps;
-        @GuardedBy("mLock")
-        private int mSuccessfulVibratorOnSteps;
-        @GuardedBy("mLock")
-        private boolean mWaitToProcessVibratorCompleteCallbacks;
-
-        public void initializeForEffect(@NonNull CombinedVibration.Sequential vibration) {
-            synchronized (mLock) {
-                mPendingVibrateSteps++;
-                mNextSteps.offer(new StartVibrateStep(vibration));
-            }
-        }
-
-        public boolean isFinished() {
-            synchronized (mLock) {
-                return mPendingOnVibratorCompleteSteps.isEmpty() && mNextSteps.isEmpty();
-            }
-        }
-
-        /**
-         * Calculate the {@link Vibration.Status} based on the current queue state and the expected
-         * number of {@link StartVibrateStep} to be played.
-         */
-        public Vibration.Status calculateVibrationStatus(int expectedStartVibrateSteps) {
-            synchronized (mLock) {
-                if (mPendingVibrateSteps > 0
-                        || mConsumedStartVibrateSteps < expectedStartVibrateSteps) {
-                    return Vibration.Status.RUNNING;
-                }
-                if (mSuccessfulVibratorOnSteps > 0) {
-                    return Vibration.Status.FINISHED;
-                }
-                // If no step was able to turn the vibrator ON successfully.
-                return Vibration.Status.IGNORED_UNSUPPORTED;
-            }
-        }
-
-        /** Returns the time in millis to wait before calling {@link #runNextStep()}. */
-        @GuardedBy("VibrationThread.this.mLock")
-        public long getWaitMillisBeforeNextStep() {
-            if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
-                // Steps resumed by vibrator complete callback should be played right away.
-                return 0;
-            }
-            Step nextStep = mNextSteps.peek();
-            return nextStep == null ? 0 : nextStep.calculateWaitTime();
-        }
-
-        /**
-         * Play and remove the step at the top of this queue, and also adds the next steps generated
-         * to be played next.
-         */
-        public void runNextStep() {
-            // Vibrator callbacks should wait until the polled step is played and the next steps are
-            // added back to the queue, so they can handle the callback.
-            markWaitToProcessVibratorCallbacks();
-            try {
-                Step nextStep = pollNext();
-                if (nextStep != null) {
-                    // This might turn on the vibrator and have a HAL latency. Execute this outside
-                    // any lock to avoid blocking other interactions with the thread.
-                    List<Step> nextSteps = nextStep.play();
-                    synchronized (mLock) {
-                        if (nextStep.getVibratorOnDuration() > 0) {
-                            mSuccessfulVibratorOnSteps++;
-                        }
-                        if (nextStep instanceof StartVibrateStep) {
-                            mConsumedStartVibrateSteps++;
-                        }
-                        if (!nextStep.isCleanUp()) {
-                            mPendingVibrateSteps--;
-                        }
-                        for (int i = 0; i < nextSteps.size(); i++) {
-                            mPendingVibrateSteps += nextSteps.get(i).isCleanUp() ? 0 : 1;
-                        }
-                        mNextSteps.addAll(nextSteps);
-                    }
-                }
-            } finally {
-                synchronized (mLock) {
-                    processVibratorCompleteCallbacks();
-                }
-            }
-        }
-
-        /**
-         * Notify the vibrator completion.
-         *
-         * <p>This is a lightweight method that do not trigger any operation from {@link
-         * VibratorController}, so it can be called directly from a native callback.
-         */
-        @GuardedBy("mLock")
-        public void notifyVibratorComplete(int vibratorId) {
-            mCompletionNotifiedVibrators.offer(vibratorId);
-            if (!mWaitToProcessVibratorCompleteCallbacks) {
-                // No step is being played or cancelled now, process the callback right away.
-                processVibratorCompleteCallbacks();
-            }
-        }
-
-        /**
-         * Cancel the current queue, replacing all remaining steps with respective clean-up steps.
-         *
-         * <p>This will remove all steps and replace them with respective
-         * {@link Step#cancel()}.
-         */
-        public void cancel() {
-            // Vibrator callbacks should wait until all steps from the queue are properly cancelled
-            // and clean up steps are added back to the queue, so they can handle the callback.
-            markWaitToProcessVibratorCallbacks();
-            try {
-                List<Step> cleanUpSteps = new ArrayList<>();
-                Step step;
-                while ((step = pollNext()) != null) {
-                    cleanUpSteps.addAll(step.cancel());
-                }
-                synchronized (mLock) {
-                    // All steps generated by Step.cancel() should be clean-up steps.
-                    mPendingVibrateSteps = 0;
-                    mNextSteps.addAll(cleanUpSteps);
-                }
-            } finally {
-                synchronized (mLock) {
-                    processVibratorCompleteCallbacks();
-                }
-            }
-        }
-
-        /**
-         * Cancel the current queue immediately, clearing all remaining steps and skipping clean-up.
-         *
-         * <p>This will remove and trigger {@link Step#cancelImmediately()} in all steps, in order.
-         */
-        public void cancelImmediately() {
-            // Vibrator callbacks should wait until all steps from the queue are properly cancelled.
-            markWaitToProcessVibratorCallbacks();
-            try {
-                Step step;
-                while ((step = pollNext()) != null) {
-                    // This might turn off the vibrator and have a HAL latency. Execute this outside
-                    // any lock to avoid blocking other interactions with the thread.
-                    step.cancelImmediately();
-                }
-                synchronized (mLock) {
-                    mPendingVibrateSteps = 0;
-                }
-            } finally {
-                synchronized (mLock) {
-                    processVibratorCompleteCallbacks();
-                }
-            }
-        }
-
-        @Nullable
-        private Step pollNext() {
-            synchronized (mLock) {
-                // Prioritize the steps resumed by a vibrator complete callback.
-                if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
-                    return mPendingOnVibratorCompleteSteps.poll();
-                }
-                return mNextSteps.poll();
-            }
-        }
-
-        private void markWaitToProcessVibratorCallbacks() {
-            synchronized (mLock) {
-                mWaitToProcessVibratorCompleteCallbacks = true;
-            }
-        }
-
-        /**
-         * Notify the step in this queue that should be resumed by the vibrator completion
-         * callback and keep it separate to be consumed by {@link #runNextStep()}.
-         *
-         * <p>This is a lightweight method that do not trigger any operation from {@link
-         * VibratorController}, so it can be called directly from a native callback.
-         *
-         * <p>This assumes only one of the next steps is waiting on this given vibrator, so the
-         * first step found will be resumed by this method, in no particular order.
-         */
-        @GuardedBy("mLock")
-        private void processVibratorCompleteCallbacks() {
-            mWaitToProcessVibratorCompleteCallbacks = false;
-            while (!mCompletionNotifiedVibrators.isEmpty()) {
-                int vibratorId = mCompletionNotifiedVibrators.poll();
-                Iterator<Step> it = mNextSteps.iterator();
-                while (it.hasNext()) {
-                    Step step = it.next();
-                    if (step.acceptVibratorCompleteCallback(vibratorId)) {
-                        it.remove();
-                        mPendingOnVibratorCompleteSteps.offer(step);
-                        break;
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Represent a single step for playing a vibration.
-     *
-     * <p>Every step has a start time, which can be used to apply delays between steps while
-     * executing them in sequence.
-     */
-    private abstract class Step implements Comparable<Step> {
-        public final long startTime;
-
-        Step(long startTime) {
-            this.startTime = startTime;
-        }
-
-        /**
-         * Returns true if this step is a clean up step and not part of a {@link VibrationEffect} or
-         * {@link CombinedVibration}.
-         */
-        public boolean isCleanUp() {
-            return false;
-        }
-
-        /** Play this step, returning a (possibly empty) list of next steps. */
-        @NonNull
-        public abstract List<Step> play();
-
-        /**
-         * Cancel this pending step and return a (possibly empty) list of clean-up steps that should
-         * be played to gracefully cancel this step.
-         */
-        @NonNull
-        public abstract List<Step> cancel();
-
-        /** Cancel this pending step immediately, skipping any clean-up. */
-        public abstract void cancelImmediately();
-
-        /**
-         * Return the duration the vibrator was turned on when this step was played.
-         *
-         * @return A positive duration that the vibrator was turned on for by this step;
-         * Zero if the segment is not supported, the step was not played yet or vibrator was never
-         * turned on by this step; A negative value if the vibrator call has failed.
-         */
-        public long getVibratorOnDuration() {
-            return 0;
-        }
-
-        /**
-         * Return true to run this step right after a vibrator has notified vibration completed,
-         * used to resume steps waiting on vibrator callbacks with a timeout.
-         */
-        public boolean acceptVibratorCompleteCallback(int vibratorId) {
-            return false;
-        }
-
-        /**
-         * Returns the time in millis to wait before playing this step. This is performed
-         * while holding the queue lock, so should not rely on potentially slow operations.
-         */
-        public long calculateWaitTime() {
-            if (startTime == Long.MAX_VALUE) {
-                // This step don't have a predefined start time, it's just marked to be executed
-                // after all other steps have finished.
-                return 0;
-            }
-            return Math.max(0, startTime - SystemClock.uptimeMillis());
-        }
-
-        @Override
-        public int compareTo(Step o) {
-            return Long.compare(startTime, o.startTime);
-        }
-    }
-
-    /**
-     * Starts a sync vibration.
-     *
-     * <p>If this step has successfully started playing a vibration on any vibrator, it will always
-     * add a {@link FinishVibrateStep} to the queue, to be played after all vibrators have finished
-     * all their individual steps.
-     *
-     * <p>If this step does not start any vibrator, it will add a {@link StartVibrateStep} if the
-     * sequential effect isn't finished yet.
-     */
-    private final class StartVibrateStep extends Step {
-        public final CombinedVibration.Sequential sequentialEffect;
-        public final int currentIndex;
-
-        private long mVibratorsOnMaxDuration;
-
-        StartVibrateStep(CombinedVibration.Sequential effect) {
-            this(SystemClock.uptimeMillis() + effect.getDelays().get(0), effect, /* index= */ 0);
-        }
-
-        StartVibrateStep(long startTime, CombinedVibration.Sequential effect, int index) {
-            super(startTime);
-            sequentialEffect = effect;
-            currentIndex = index;
-        }
-
-        @Override
-        public long getVibratorOnDuration() {
-            return mVibratorsOnMaxDuration;
-        }
-
-        @Override
-        public List<Step> play() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "StartVibrateStep");
-            List<Step> nextSteps = new ArrayList<>();
-            mVibratorsOnMaxDuration = -1;
-            try {
-                if (DEBUG) {
-                    Slog.d(TAG, "StartVibrateStep for effect #" + currentIndex);
-                }
-                CombinedVibration effect = sequentialEffect.getEffects().get(currentIndex);
-                DeviceEffectMap effectMapping = createEffectToVibratorMapping(effect);
-                if (effectMapping == null) {
-                    // Unable to map effects to vibrators, ignore this step.
-                    return nextSteps;
-                }
-
-                mVibratorsOnMaxDuration = startVibrating(effectMapping, nextSteps);
-                noteVibratorOn(mVibratorsOnMaxDuration);
-            } finally {
-                if (mVibratorsOnMaxDuration >= 0) {
-                    // It least one vibrator was started then add a finish step to wait for all
-                    // active vibrators to finish their individual steps before going to the next.
-                    // Otherwise this step was ignored so just go to the next one.
-                    Step nextStep =
-                            mVibratorsOnMaxDuration > 0 ? new FinishVibrateStep(this) : nextStep();
-                    if (nextStep != null) {
-                        nextSteps.add(nextStep);
-                    }
-                }
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-            return nextSteps;
-        }
-
-        @Override
-        public List<Step> cancel() {
-            return EMPTY_STEP_LIST;
-        }
-
-        @Override
-        public void cancelImmediately() {
-        }
-
-        /**
-         * Create the next {@link StartVibrateStep} to play this sequential effect, starting at the
-         * time this method is called, or null if sequence is complete.
-         */
-        @Nullable
-        private Step nextStep() {
-            int nextIndex = currentIndex + 1;
-            if (nextIndex >= sequentialEffect.getEffects().size()) {
-                return null;
-            }
-            long nextEffectDelay = sequentialEffect.getDelays().get(nextIndex);
-            long nextStartTime = SystemClock.uptimeMillis() + nextEffectDelay;
-            return new StartVibrateStep(nextStartTime, sequentialEffect, nextIndex);
-        }
-
-        /** Create a mapping of individual {@link VibrationEffect} to available vibrators. */
-        @Nullable
-        private DeviceEffectMap createEffectToVibratorMapping(
-                CombinedVibration effect) {
-            if (effect instanceof CombinedVibration.Mono) {
-                return new DeviceEffectMap((CombinedVibration.Mono) effect);
-            }
-            if (effect instanceof CombinedVibration.Stereo) {
-                return new DeviceEffectMap((CombinedVibration.Stereo) effect);
-            }
-            return null;
-        }
-
-        /**
-         * Starts playing effects on designated vibrators, in sync.
-         *
-         * @param effectMapping The {@link CombinedVibration} mapped to this device vibrators
-         * @param nextSteps     An output list to accumulate the future {@link Step Steps} created
-         *                      by this method, typically one for each vibrator that has
-         *                      successfully started vibrating on this step.
-         * @return The duration, in millis, of the {@link CombinedVibration}. Repeating
-         * waveforms return {@link Long#MAX_VALUE}. Zero or negative values indicate the vibrators
-         * have ignored all effects.
-         */
-        private long startVibrating(DeviceEffectMap effectMapping, List<Step> nextSteps) {
-            int vibratorCount = effectMapping.size();
-            if (vibratorCount == 0) {
-                // No effect was mapped to any available vibrator.
-                return 0;
-            }
-
-            SingleVibratorStep[] steps = new SingleVibratorStep[vibratorCount];
-            long vibrationStartTime = SystemClock.uptimeMillis();
-            for (int i = 0; i < vibratorCount; i++) {
-                steps[i] = nextVibrateStep(vibrationStartTime,
-                        mVibrators.get(effectMapping.vibratorIdAt(i)),
-                        effectMapping.effectAt(i),
-                        /* segmentIndex= */ 0, /* vibratorOffTimeout= */ 0);
-            }
-
-            if (steps.length == 1) {
-                // No need to prepare and trigger sync effects on a single vibrator.
-                return startVibrating(steps[0], nextSteps);
-            }
-
-            // This synchronization of vibrators should be executed one at a time, even if we are
-            // vibrating different sets of vibrators in parallel. The manager can only prepareSynced
-            // one set of vibrators at a time.
-            synchronized (mLock) {
-                boolean hasPrepared = false;
-                boolean hasTriggered = false;
-                long maxDuration = 0;
-                try {
-                    hasPrepared = mVibratorManagerHooks.prepareSyncedVibration(
-                            effectMapping.getRequiredSyncCapabilities(),
-                            effectMapping.getVibratorIds());
-
-                    for (SingleVibratorStep step : steps) {
-                        long duration = startVibrating(step, nextSteps);
-                        if (duration < 0) {
-                            // One vibrator has failed, fail this entire sync attempt.
-                            return maxDuration = -1;
-                        }
-                        maxDuration = Math.max(maxDuration, duration);
-                    }
-
-                    // Check if sync was prepared and if any step was accepted by a vibrator,
-                    // otherwise there is nothing to trigger here.
-                    if (hasPrepared && maxDuration > 0) {
-                        hasTriggered = mVibratorManagerHooks.triggerSyncedVibration(mVibration.id);
-                    }
-                    return maxDuration;
-                } finally {
-                    if (hasPrepared && !hasTriggered) {
-                        // Trigger has failed or all steps were ignored by the vibrators.
-                        mVibratorManagerHooks.cancelSyncedVibration();
-                        nextSteps.clear();
-                    } else if (maxDuration < 0) {
-                        // Some vibrator failed without being prepared so other vibrators might be
-                        // active. Cancel and remove every pending step from output list.
-                        for (int i = nextSteps.size() - 1; i >= 0; i--) {
-                            nextSteps.remove(i).cancelImmediately();
-                        }
-                    }
-                }
-            }
-        }
-
-        private long startVibrating(SingleVibratorStep step, List<Step> nextSteps) {
-            nextSteps.addAll(step.play());
-            long stepDuration = step.getVibratorOnDuration();
-            if (stepDuration < 0) {
-                // Step failed, so return negative duration to propagate failure.
-                return stepDuration;
-            }
-            // Return the longest estimation for the entire effect.
-            return Math.max(stepDuration, step.effect.getDuration());
-        }
-    }
-
-    /**
-     * Finish a sync vibration started by a {@link StartVibrateStep}.
-     *
-     * <p>This only plays after all active vibrators steps have finished, and adds a {@link
-     * StartVibrateStep} to the queue if the sequential effect isn't finished yet.
-     */
-    private final class FinishVibrateStep extends Step {
-        public final StartVibrateStep startedStep;
-
-        FinishVibrateStep(StartVibrateStep startedStep) {
-            super(Long.MAX_VALUE); // No predefined startTime, just wait for all steps in the queue.
-            this.startedStep = startedStep;
-        }
-
-        @Override
-        public boolean isCleanUp() {
-            // This step only notes that all the vibrators has been turned off.
-            return true;
-        }
-
-        @Override
-        public List<Step> play() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "FinishVibrateStep");
-            try {
-                if (DEBUG) {
-                    Slog.d(TAG, "FinishVibrateStep for effect #" + startedStep.currentIndex);
-                }
-                noteVibratorOff();
-                Step nextStep = startedStep.nextStep();
-                return nextStep == null ? EMPTY_STEP_LIST : Arrays.asList(nextStep);
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-
-        @Override
-        public List<Step> cancel() {
-            cancelImmediately();
-            return EMPTY_STEP_LIST;
-        }
-
-        @Override
-        public void cancelImmediately() {
-            noteVibratorOff();
-        }
-    }
-
-    /**
-     * Represent a step on a single vibrator that plays one or more segments from a
-     * {@link VibrationEffect.Composed} effect.
-     */
-    private abstract class SingleVibratorStep extends Step {
-        public final VibratorController controller;
-        public final VibrationEffect.Composed effect;
-        public final int segmentIndex;
-        public final long vibratorOffTimeout;
-
-        long mVibratorOnResult;
-        boolean mVibratorCompleteCallbackReceived;
-
-        /**
-         * @param startTime          The time to schedule this step in the {@link StepQueue}.
-         * @param controller         The vibrator that is playing the effect.
-         * @param effect             The effect being played in this step.
-         * @param index              The index of the next segment to be played by this step
-         * @param vibratorOffTimeout The time the vibrator is expected to complete any previous
-         *                           vibration and turn off. This is used to allow this step to be
-         *                           triggered when the completion callback is received, and can
-         *                           be used play effects back-to-back.
-         */
-        SingleVibratorStep(long startTime, VibratorController controller,
-                VibrationEffect.Composed effect, int index, long vibratorOffTimeout) {
-            super(startTime);
-            this.controller = controller;
-            this.effect = effect;
-            this.segmentIndex = index;
-            this.vibratorOffTimeout = vibratorOffTimeout;
-        }
-
-        @Override
-        public long getVibratorOnDuration() {
-            return mVibratorOnResult;
-        }
-
-        @Override
-        public boolean acceptVibratorCompleteCallback(int vibratorId) {
-            boolean isSameVibrator = controller.getVibratorInfo().getId() == vibratorId;
-            mVibratorCompleteCallbackReceived |= isSameVibrator;
-            // Only activate this step if a timeout was set to wait for the vibration to complete,
-            // otherwise we are waiting for the correct time to play the next step.
-            return isSameVibrator && (vibratorOffTimeout > SystemClock.uptimeMillis());
-        }
-
-        @Override
-        public List<Step> cancel() {
-            return Arrays.asList(new EffectCompleteStep(SystemClock.uptimeMillis(),
-                    /* cancelled= */ true, controller, vibratorOffTimeout));
-        }
-
-        @Override
-        public void cancelImmediately() {
-            if (vibratorOffTimeout > SystemClock.uptimeMillis()) {
-                // Vibrator might be running from previous steps, so turn it off while canceling.
-                stopVibrating();
-            }
-        }
-
-        void stopVibrating() {
-            if (DEBUG) {
-                Slog.d(TAG, "Turning off vibrator " + controller.getVibratorInfo().getId());
-            }
-            controller.off();
-        }
-
-        void changeAmplitude(float amplitude) {
-            if (DEBUG) {
-                Slog.d(TAG, "Amplitude changed on vibrator " + controller.getVibratorInfo().getId()
-                        + " to " + amplitude);
-            }
-            controller.setAmplitude(amplitude);
-        }
-
-        /** Return the {@link #nextVibrateStep} with same timings, only jumping the segments. */
-        public List<Step> skipToNextSteps(int segmentsSkipped) {
-            return nextSteps(startTime, vibratorOffTimeout, segmentsSkipped);
-        }
-
-        /**
-         * Return the {@link #nextVibrateStep} with same start and off timings calculated from
-         * {@link #getVibratorOnDuration()}, jumping all played segments.
-         *
-         * <p>This method has same behavior as {@link #skipToNextSteps(int)} when the vibrator
-         * result is non-positive, meaning the vibrator has either ignored or failed to turn on.
-         */
-        public List<Step> nextSteps(int segmentsPlayed) {
-            if (mVibratorOnResult <= 0) {
-                // Vibration was not started, so just skip the played segments and keep timings.
-                return skipToNextSteps(segmentsPlayed);
-            }
-            long nextStartTime = SystemClock.uptimeMillis() + mVibratorOnResult;
-            long nextVibratorOffTimeout = nextStartTime + CALLBACKS_EXTRA_TIMEOUT;
-            return nextSteps(nextStartTime, nextVibratorOffTimeout, segmentsPlayed);
-        }
-
-        /**
-         * Return the {@link #nextVibrateStep} with given start and off timings, which might be
-         * calculated independently, jumping all played segments.
-         *
-         * <p>This should be used when the vibrator on/off state is not responsible for the steps
-         * execution timings, e.g. while playing the vibrator amplitudes.
-         */
-        public List<Step> nextSteps(long nextStartTime, long vibratorOffTimeout,
-                int segmentsPlayed) {
-            Step nextStep = nextVibrateStep(nextStartTime, controller, effect,
-                    segmentIndex + segmentsPlayed, vibratorOffTimeout);
-            return nextStep == null ? EMPTY_STEP_LIST : Arrays.asList(nextStep);
-        }
-    }
-
-    /**
-     * Represent a step turn the vibrator on with a single prebaked effect.
-     *
-     * <p>This step automatically falls back by replacing the prebaked segment with
-     * {@link VibrationSettings#getFallbackEffect(int)}, if available.
-     */
-    private final class PerformStep extends SingleVibratorStep {
-
-        PerformStep(long startTime, VibratorController controller,
-                VibrationEffect.Composed effect, int index, long vibratorOffTimeout) {
-            // This step should wait for the last vibration to finish (with the timeout) and for the
-            // intended step start time (to respect the effect delays).
-            super(Math.max(startTime, vibratorOffTimeout), controller, effect, index,
-                    vibratorOffTimeout);
-        }
-
-        @Override
-        public List<Step> play() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "PerformStep");
-            try {
-                VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
-                if (!(segment instanceof PrebakedSegment)) {
-                    Slog.w(TAG, "Ignoring wrong segment for a PerformStep: " + segment);
-                    return skipToNextSteps(/* segmentsSkipped= */ 1);
-                }
-
-                PrebakedSegment prebaked = (PrebakedSegment) segment;
-                if (DEBUG) {
-                    Slog.d(TAG, "Perform " + VibrationEffect.effectIdToString(
-                            prebaked.getEffectId()) + " on vibrator "
-                            + controller.getVibratorInfo().getId());
-                }
-
-                VibrationEffect fallback = mVibration.getFallback(prebaked.getEffectId());
-                mVibratorOnResult = controller.on(prebaked, mVibration.id);
-
-                if (mVibratorOnResult == 0 && prebaked.shouldFallback()
-                        && (fallback instanceof VibrationEffect.Composed)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Playing fallback for effect "
-                                + VibrationEffect.effectIdToString(prebaked.getEffectId()));
-                    }
-                    SingleVibratorStep fallbackStep = nextVibrateStep(startTime, controller,
-                            replaceCurrentSegment((VibrationEffect.Composed) fallback),
-                            segmentIndex, vibratorOffTimeout);
-                    List<Step> fallbackResult = fallbackStep.play();
-                    // Update the result with the fallback result so this step is seamlessly
-                    // replaced by the fallback to any outer application of this.
-                    mVibratorOnResult = fallbackStep.getVibratorOnDuration();
-                    return fallbackResult;
-                }
-
-                return nextSteps(/* segmentsPlayed= */ 1);
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-
-        /**
-         * Replace segment at {@link #segmentIndex} in {@link #effect} with given fallback segments.
-         *
-         * @return a copy of {@link #effect} with replaced segment.
-         */
-        private VibrationEffect.Composed replaceCurrentSegment(VibrationEffect.Composed fallback) {
-            List<VibrationEffectSegment> newSegments = new ArrayList<>(effect.getSegments());
-            int newRepeatIndex = effect.getRepeatIndex();
-            newSegments.remove(segmentIndex);
-            newSegments.addAll(segmentIndex, fallback.getSegments());
-            if (segmentIndex < effect.getRepeatIndex()) {
-                newRepeatIndex += fallback.getSegments().size() - 1;
-            }
-            return new VibrationEffect.Composed(newSegments, newRepeatIndex);
-        }
-    }
-
-    /**
-     * Represent a step turn the vibrator on using a composition of primitives.
-     *
-     * <p>This step will use the maximum supported number of consecutive segments of type
-     * {@link PrimitiveSegment} starting at the current index.
-     */
-    private final class ComposePrimitivesStep extends SingleVibratorStep {
-
-        ComposePrimitivesStep(long startTime, VibratorController controller,
-                VibrationEffect.Composed effect, int index, long vibratorOffTimeout) {
-            // This step should wait for the last vibration to finish (with the timeout) and for the
-            // intended step start time (to respect the effect delays).
-            super(Math.max(startTime, vibratorOffTimeout), controller, effect, index,
-                    vibratorOffTimeout);
-        }
-
-        @Override
-        public List<Step> play() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePrimitivesStep");
-            try {
-                // Load the next PrimitiveSegments to create a single compose call to the vibrator,
-                // limited to the vibrator composition maximum size.
-                int limit = controller.getVibratorInfo().getCompositionSizeMax();
-                int segmentCount = limit > 0
-                        ? Math.min(effect.getSegments().size(), segmentIndex + limit)
-                        : effect.getSegments().size();
-                List<PrimitiveSegment> primitives = new ArrayList<>();
-                for (int i = segmentIndex; i < segmentCount; i++) {
-                    VibrationEffectSegment segment = effect.getSegments().get(i);
-                    if (segment instanceof PrimitiveSegment) {
-                        primitives.add((PrimitiveSegment) segment);
-                    } else {
-                        break;
-                    }
-                }
-
-                if (primitives.isEmpty()) {
-                    Slog.w(TAG, "Ignoring wrong segment for a ComposePrimitivesStep: "
-                            + effect.getSegments().get(segmentIndex));
-                    return skipToNextSteps(/* segmentsSkipped= */ 1);
-                }
-
-                if (DEBUG) {
-                    Slog.d(TAG, "Compose " + primitives + " primitives on vibrator "
-                            + controller.getVibratorInfo().getId());
-                }
-                mVibratorOnResult = controller.on(
-                        primitives.toArray(new PrimitiveSegment[primitives.size()]),
-                        mVibration.id);
-
-                return nextSteps(/* segmentsPlayed= */ primitives.size());
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-    }
-
-    /**
-     * Represent a step turn the vibrator on using a composition of PWLE segments.
-     *
-     * <p>This step will use the maximum supported number of consecutive segments of type
-     * {@link StepSegment} or {@link RampSegment} starting at the current index.
-     */
-    private final class ComposePwleStep extends SingleVibratorStep {
-
-        ComposePwleStep(long startTime, VibratorController controller,
-                VibrationEffect.Composed effect, int index, long vibratorOffTimeout) {
-            // This step should wait for the last vibration to finish (with the timeout) and for the
-            // intended step start time (to respect the effect delays).
-            super(Math.max(startTime, vibratorOffTimeout), controller, effect, index,
-                    vibratorOffTimeout);
-        }
-
-        @Override
-        public List<Step> play() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePwleStep");
-            try {
-                // Load the next RampSegments to create a single composePwle call to the vibrator,
-                // limited to the vibrator PWLE maximum size.
-                int limit = controller.getVibratorInfo().getPwleSizeMax();
-                int segmentCount = limit > 0
-                        ? Math.min(effect.getSegments().size(), segmentIndex + limit)
-                        : effect.getSegments().size();
-                List<RampSegment> pwles = new ArrayList<>();
-                for (int i = segmentIndex; i < segmentCount; i++) {
-                    VibrationEffectSegment segment = effect.getSegments().get(i);
-                    if (segment instanceof RampSegment) {
-                        pwles.add((RampSegment) segment);
-                    } else {
-                        break;
-                    }
-                }
-
-                if (pwles.isEmpty()) {
-                    Slog.w(TAG, "Ignoring wrong segment for a ComposePwleStep: "
-                            + effect.getSegments().get(segmentIndex));
-                    return skipToNextSteps(/* segmentsSkipped= */ 1);
-                }
-
-                if (DEBUG) {
-                    Slog.d(TAG, "Compose " + pwles + " PWLEs on vibrator "
-                            + controller.getVibratorInfo().getId());
-                }
-                mVibratorOnResult = controller.on(pwles.toArray(new RampSegment[pwles.size()]),
-                        mVibration.id);
-
-                return nextSteps(/* segmentsPlayed= */ pwles.size());
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-    }
-
-    /**
-     * Represents a step to complete a {@link VibrationEffect}.
-     *
-     * <p>This runs right at the time the vibration is considered to end and will update the pending
-     * vibrators count. This can turn off the vibrator or slowly ramp it down to zero amplitude.
-     */
-    private final class EffectCompleteStep extends SingleVibratorStep {
-        private final boolean mCancelled;
-
-        EffectCompleteStep(long startTime, boolean cancelled, VibratorController controller,
-                long vibratorOffTimeout) {
-            super(startTime, controller, /* effect= */ null, /* index= */ -1, vibratorOffTimeout);
-            mCancelled = cancelled;
-        }
-
-        @Override
-        public boolean isCleanUp() {
-            // If the vibration was cancelled then this is just a clean up to ramp off the vibrator.
-            // Otherwise this step is part of the vibration.
-            return mCancelled;
-        }
-
-        @Override
-        public List<Step> cancel() {
-            if (mCancelled) {
-                // Double cancelling will just turn off the vibrator right away.
-                return Arrays.asList(new OffStep(SystemClock.uptimeMillis(), controller));
-            }
-            return super.cancel();
-        }
-
-        @Override
-        public List<Step> play() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "EffectCompleteStep");
-            try {
-                if (DEBUG) {
-                    Slog.d(TAG, "Running " + (mCancelled ? "cancel" : "complete") + " vibration"
-                            + " step on vibrator " + controller.getVibratorInfo().getId());
-                }
-                if (mVibratorCompleteCallbackReceived) {
-                    // Vibration completion callback was received by this step, just turn if off
-                    // and skip any clean-up.
-                    stopVibrating();
-                    return EMPTY_STEP_LIST;
-                }
-
-                float currentAmplitude = controller.getCurrentAmplitude();
-                long remainingOnDuration =
-                        vibratorOffTimeout - CALLBACKS_EXTRA_TIMEOUT - SystemClock.uptimeMillis();
-                long rampDownDuration =
-                        Math.min(remainingOnDuration, mVibrationSettings.getRampDownDuration());
-                long stepDownDuration = mVibrationSettings.getRampStepDuration();
-                if (currentAmplitude < RAMP_OFF_AMPLITUDE_MIN
-                        || rampDownDuration <= stepDownDuration) {
-                    // No need to ramp down the amplitude, just wait to turn it off.
-                    if (mCancelled) {
-                        // Vibration is completing because it was cancelled, turn off right away.
-                        stopVibrating();
-                        return EMPTY_STEP_LIST;
-                    } else {
-                        return Arrays.asList(new OffStep(vibratorOffTimeout, controller));
-                    }
-                }
-
-                if (DEBUG) {
-                    Slog.d(TAG, "Ramping down vibrator " + controller.getVibratorInfo().getId()
-                            + " from amplitude " + currentAmplitude
-                            + " for " + rampDownDuration + "ms");
-                }
-                float amplitudeDelta = currentAmplitude / (rampDownDuration / stepDownDuration);
-                float amplitudeTarget = currentAmplitude - amplitudeDelta;
-                long newVibratorOffTimeout = mCancelled ? rampDownDuration : vibratorOffTimeout;
-                return Arrays.asList(new RampOffStep(startTime, amplitudeTarget, amplitudeDelta,
-                        controller, newVibratorOffTimeout));
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-    }
-
-    /** Represents a step to ramp down the vibrator amplitude before turning it off. */
-    private final class RampOffStep extends SingleVibratorStep {
-        private final float mAmplitudeTarget;
-        private final float mAmplitudeDelta;
-
-        RampOffStep(long startTime, float amplitudeTarget, float amplitudeDelta,
-                VibratorController controller, long vibratorOffTimeout) {
-            super(startTime, controller, /* effect= */ null, /* index= */ -1, vibratorOffTimeout);
-            mAmplitudeTarget = amplitudeTarget;
-            mAmplitudeDelta = amplitudeDelta;
-        }
-
-        @Override
-        public boolean isCleanUp() {
-            return true;
-        }
-
-        @Override
-        public List<Step> cancel() {
-            return Arrays.asList(new OffStep(SystemClock.uptimeMillis(), controller));
-        }
-
-        @Override
-        public List<Step> play() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "RampOffStep");
-            try {
-                if (DEBUG) {
-                    long latency = SystemClock.uptimeMillis() - startTime;
-                    Slog.d(TAG, "Ramp down the vibrator amplitude, step with "
-                            + latency + "ms latency.");
-                }
-                if (mVibratorCompleteCallbackReceived) {
-                    // Vibration completion callback was received by this step, just turn if off
-                    // and skip the rest of the steps to ramp down the vibrator amplitude.
-                    stopVibrating();
-                    return EMPTY_STEP_LIST;
-                }
-
-                changeAmplitude(mAmplitudeTarget);
-
-                float newAmplitudeTarget = mAmplitudeTarget - mAmplitudeDelta;
-                if (newAmplitudeTarget < RAMP_OFF_AMPLITUDE_MIN) {
-                    // Vibrator amplitude cannot go further down, just turn it off.
-                    return Arrays.asList(new OffStep(vibratorOffTimeout, controller));
-                }
-                return Arrays.asList(new RampOffStep(
-                        startTime + mVibrationSettings.getRampStepDuration(), newAmplitudeTarget,
-                        mAmplitudeDelta, controller, vibratorOffTimeout));
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-    }
-
-    /**
-     * Represents a step to turn the vibrator off.
-     *
-     * <p>This runs after a timeout on the expected time the vibrator should have finished playing,
-     * and can be brought forward by vibrator complete callbacks.
-     */
-    private final class OffStep extends SingleVibratorStep {
-
-        OffStep(long startTime, VibratorController controller) {
-            super(startTime, controller, /* effect= */ null, /* index= */ -1, startTime);
-        }
-
-        @Override
-        public boolean isCleanUp() {
-            return true;
-        }
-
-        @Override
-        public List<Step> cancel() {
-            return Arrays.asList(new OffStep(SystemClock.uptimeMillis(), controller));
-        }
-
-        @Override
-        public void cancelImmediately() {
-            stopVibrating();
-        }
-
-        @Override
-        public List<Step> play() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "OffStep");
-            try {
-                stopVibrating();
-                return EMPTY_STEP_LIST;
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-    }
-
-    /**
-     * Represents a step to turn the vibrator on and change its amplitude.
-     *
-     * <p>This step ignores vibration completion callbacks and control the vibrator on/off state
-     * and amplitude to simulate waveforms represented by a sequence of {@link StepSegment}.
-     */
-    private final class AmplitudeStep extends SingleVibratorStep {
-        private long mNextOffTime;
-
-        AmplitudeStep(long startTime, VibratorController controller,
-                VibrationEffect.Composed effect, int index, long vibratorOffTimeout) {
-            // This step has a fixed startTime coming from the timings of the waveform it's playing.
-            super(startTime, controller, effect, index, vibratorOffTimeout);
-            mNextOffTime = vibratorOffTimeout;
-        }
-
-        @Override
-        public boolean acceptVibratorCompleteCallback(int vibratorId) {
-            if (controller.getVibratorInfo().getId() == vibratorId) {
-                mVibratorCompleteCallbackReceived = true;
-                mNextOffTime = SystemClock.uptimeMillis();
-            }
-            // Timings are tightly controlled here, so only trigger this step if the vibrator was
-            // supposed to be ON but has completed prematurely, to turn it back on as soon as
-            // possible.
-            return mNextOffTime < startTime && controller.getCurrentAmplitude() > 0;
-        }
-
-        @Override
-        public List<Step> play() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "AmplitudeStep");
-            try {
-                long now = SystemClock.uptimeMillis();
-                long latency = now - startTime;
-                if (DEBUG) {
-                    Slog.d(TAG, "Running amplitude step with " + latency + "ms latency.");
-                }
-
-                if (mVibratorCompleteCallbackReceived && latency < 0) {
-                    // This step was run early because the vibrator turned off prematurely.
-                    // Turn it back on and return this same step to run at the exact right time.
-                    mNextOffTime = turnVibratorBackOn(/* remainingDuration= */ -latency);
-                    return Arrays.asList(new AmplitudeStep(startTime, controller, effect,
-                            segmentIndex, mNextOffTime));
-                }
-
-                VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
-                if (!(segment instanceof StepSegment)) {
-                    Slog.w(TAG, "Ignoring wrong segment for a AmplitudeStep: " + segment);
-                    return skipToNextSteps(/* segmentsSkipped= */ 1);
-                }
-
-                StepSegment stepSegment = (StepSegment) segment;
-                if (stepSegment.getDuration() == 0) {
-                    // Skip waveform entries with zero timing.
-                    return skipToNextSteps(/* segmentsSkipped= */ 1);
-                }
-
-                float amplitude = stepSegment.getAmplitude();
-                if (amplitude == 0) {
-                    if (vibratorOffTimeout > now) {
-                        // Amplitude cannot be set to zero, so stop the vibrator.
-                        stopVibrating();
-                        mNextOffTime = now;
-                    }
-                } else {
-                    if (startTime >= mNextOffTime) {
-                        // Vibrator is OFF. Turn vibrator back on for the duration of another
-                        // cycle before setting the amplitude.
-                        long onDuration = getVibratorOnDuration(effect, segmentIndex);
-                        if (onDuration > 0) {
-                            mVibratorOnResult = startVibrating(onDuration);
-                            mNextOffTime = now + onDuration + CALLBACKS_EXTRA_TIMEOUT;
-                        }
-                    }
-                    changeAmplitude(amplitude);
-                }
-
-                // Use original startTime to avoid propagating latencies to the waveform.
-                long nextStartTime = startTime + segment.getDuration();
-                return nextSteps(nextStartTime, mNextOffTime, /* segmentsPlayed= */ 1);
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-
-        private long turnVibratorBackOn(long remainingDuration) {
-            long onDuration = getVibratorOnDuration(effect, segmentIndex);
-            if (onDuration <= 0) {
-                // Vibrator is supposed to go back off when this step starts, so just leave it off.
-                return vibratorOffTimeout;
-            }
-            onDuration += remainingDuration;
-            float expectedAmplitude = controller.getCurrentAmplitude();
-            mVibratorOnResult = startVibrating(onDuration);
-            if (mVibratorOnResult > 0) {
-                // Set the amplitude back to the value it was supposed to be playing at.
-                changeAmplitude(expectedAmplitude);
-            }
-            return SystemClock.uptimeMillis() + onDuration + CALLBACKS_EXTRA_TIMEOUT;
-        }
-
-        private long startVibrating(long duration) {
-            if (DEBUG) {
-                Slog.d(TAG, "Turning on vibrator " + controller.getVibratorInfo().getId() + " for "
-                        + duration + "ms");
-            }
-            return controller.on(duration, mVibration.id);
-        }
-
-        /**
-         * Get the duration the vibrator will be on for a waveform, starting at {@code startIndex}
-         * until the next time it's vibrating amplitude is zero or a different type of segment is
-         * found.
-         */
-        private long getVibratorOnDuration(VibrationEffect.Composed effect, int startIndex) {
-            List<VibrationEffectSegment> segments = effect.getSegments();
-            int segmentCount = segments.size();
-            int repeatIndex = effect.getRepeatIndex();
-            int i = startIndex;
-            long timing = 0;
-            while (i < segmentCount) {
-                VibrationEffectSegment segment = segments.get(i);
-                if (!(segment instanceof StepSegment)
-                        || ((StepSegment) segment).getAmplitude() == 0) {
-                    break;
-                }
-                timing += segment.getDuration();
-                i++;
-                if (i == segmentCount && repeatIndex >= 0) {
-                    i = repeatIndex;
-                    // prevent infinite loop
-                    repeatIndex = -1;
-                }
-                if (i == startIndex) {
-                    // The repeating waveform keeps the vibrator ON all the time. Use a minimum
-                    // of 1s duration to prevent short patterns from turning the vibrator ON too
-                    // frequently.
-                    return Math.max(timing, 1000);
-                }
-            }
-            if (i == segmentCount && effect.getRepeatIndex() < 0) {
-                // Vibration ending at non-zero amplitude, add extra timings to ramp down after
-                // vibration is complete.
-                timing += mVibrationSettings.getRampDownDuration();
-            }
-            return timing;
-        }
-    }
-
-    /**
-     * Map a {@link CombinedVibration} to the vibrators available on the device.
-     *
-     * <p>This contains the logic to find the capabilities required from {@link IVibratorManager} to
-     * play all of the effects in sync.
-     */
-    private final class DeviceEffectMap {
-        private final SparseArray<VibrationEffect.Composed> mVibratorEffects;
-        private final int[] mVibratorIds;
-        private final long mRequiredSyncCapabilities;
-
-        DeviceEffectMap(CombinedVibration.Mono mono) {
-            mVibratorEffects = new SparseArray<>(mVibrators.size());
-            mVibratorIds = new int[mVibrators.size()];
-            for (int i = 0; i < mVibrators.size(); i++) {
-                int vibratorId = mVibrators.keyAt(i);
-                VibratorInfo vibratorInfo = mVibrators.valueAt(i).getVibratorInfo();
-                VibrationEffect effect = mDeviceEffectAdapter.apply(mono.getEffect(), vibratorInfo);
-                if (effect instanceof VibrationEffect.Composed) {
-                    mVibratorEffects.put(vibratorId, (VibrationEffect.Composed) effect);
-                    mVibratorIds[i] = vibratorId;
-                }
-            }
-            mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
-        }
-
-        DeviceEffectMap(CombinedVibration.Stereo stereo) {
-            SparseArray<VibrationEffect> stereoEffects = stereo.getEffects();
-            mVibratorEffects = new SparseArray<>();
-            for (int i = 0; i < stereoEffects.size(); i++) {
-                int vibratorId = stereoEffects.keyAt(i);
-                if (mVibrators.contains(vibratorId)) {
-                    VibratorInfo vibratorInfo = mVibrators.valueAt(i).getVibratorInfo();
-                    VibrationEffect effect = mDeviceEffectAdapter.apply(
-                            stereoEffects.valueAt(i), vibratorInfo);
-                    if (effect instanceof VibrationEffect.Composed) {
-                        mVibratorEffects.put(vibratorId, (VibrationEffect.Composed) effect);
-                    }
-                }
-            }
-            mVibratorIds = new int[mVibratorEffects.size()];
-            for (int i = 0; i < mVibratorEffects.size(); i++) {
-                mVibratorIds[i] = mVibratorEffects.keyAt(i);
-            }
-            mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
-        }
-
-        /**
-         * Return the number of vibrators mapped to play the {@link CombinedVibration} on this
-         * device.
-         */
-        public int size() {
-            return mVibratorIds.length;
-        }
-
-        /**
-         * Return all capabilities required to play the {@link CombinedVibration} in
-         * between calls to {@link IVibratorManager#prepareSynced} and
-         * {@link IVibratorManager#triggerSynced}.
-         */
-        public long getRequiredSyncCapabilities() {
-            return mRequiredSyncCapabilities;
-        }
-
-        /** Return all vibrator ids mapped to play the {@link CombinedVibration}. */
-        public int[] getVibratorIds() {
-            return mVibratorIds;
-        }
-
-        /** Return the id of the vibrator at given index. */
-        public int vibratorIdAt(int index) {
-            return mVibratorEffects.keyAt(index);
-        }
-
-        /** Return the {@link VibrationEffect} at given index. */
-        public VibrationEffect.Composed effectAt(int index) {
-            return mVibratorEffects.valueAt(index);
-        }
-
-        /**
-         * Return all capabilities required from the {@link IVibratorManager} to prepare and
-         * trigger all given effects in sync.
-         *
-         * @return {@link IVibratorManager#CAP_SYNC} together with all required
-         * IVibratorManager.CAP_PREPARE_* and IVibratorManager.CAP_MIXED_TRIGGER_* capabilities.
-         */
-        private long calculateRequiredSyncCapabilities(
-                SparseArray<VibrationEffect.Composed> effects) {
-            long prepareCap = 0;
-            for (int i = 0; i < effects.size(); i++) {
-                VibrationEffectSegment firstSegment = effects.valueAt(i).getSegments().get(0);
-                if (firstSegment instanceof StepSegment) {
-                    prepareCap |= IVibratorManager.CAP_PREPARE_ON;
-                } else if (firstSegment instanceof PrebakedSegment) {
-                    prepareCap |= IVibratorManager.CAP_PREPARE_PERFORM;
-                } else if (firstSegment instanceof PrimitiveSegment) {
-                    prepareCap |= IVibratorManager.CAP_PREPARE_COMPOSE;
-                }
-            }
-            int triggerCap = 0;
-            if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_ON)) {
-                triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_ON;
-            }
-            if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_PERFORM)) {
-                triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_PERFORM;
-            }
-            if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_COMPOSE)) {
-                triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_COMPOSE;
-            }
-            return IVibratorManager.CAP_SYNC | prepareCap | triggerCap;
-        }
-
-        /**
-         * Return true if {@code prepareCapabilities} contains this {@code capability} mixed with
-         * different ones, requiring a mixed trigger capability from the vibrator manager for
-         * syncing all effects.
-         */
-        private boolean requireMixedTriggerCapability(long prepareCapabilities, long capability) {
-            return (prepareCapabilities & capability) != 0
-                    && (prepareCapabilities & ~capability) != 0;
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 63f3af3..01f9d0b9 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -42,6 +42,7 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.ShellCallback;
@@ -60,6 +61,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 
@@ -88,6 +90,9 @@
             VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY
                     | VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
 
+    /** Fixed large duration used to note repeating vibrations to {@link IBatteryStats}. */
+    private static final long BATTERY_STATS_REPEATING_VIBRATION_DURATION = 5_000;
+
     /** Lifecycle responsible for initializing this class at the right system server phases. */
     public static class Lifecycle extends SystemService {
         private VibratorManagerService mService;
@@ -201,8 +206,7 @@
         mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
                 .getSystemUiServiceComponent().getPackageName();
 
-        mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
-                BatteryStats.SERVICE_NAME));
+        mBatteryStatsService = injector.getBatteryStatsService();
 
         mAppOps = mContext.getSystemService(AppOpsManager.class);
 
@@ -634,7 +638,7 @@
             }
 
             VibrationThread vibThread = new VibrationThread(vib, mVibrationSettings,
-                    mDeviceVibrationEffectAdapter, mVibrators, mWakeLock, mBatteryStatsService,
+                    mDeviceVibrationEffectAdapter, mVibrators, mWakeLock,
                     mVibrationThreadCallbacks);
 
             if (mCurrentVibration == null) {
@@ -1105,6 +1109,11 @@
             return new Handler(looper);
         }
 
+        IBatteryStats getBatteryStatsService() {
+            return IBatteryStats.Stub.asInterface(ServiceManager.getService(
+                    BatteryStats.SERVICE_NAME));
+        }
+
         VibratorController createVibratorController(int vibratorId,
                 VibratorController.OnVibrationCompleteListener listener) {
             return new VibratorController(vibratorId, listener);
@@ -1141,6 +1150,36 @@
         }
 
         @Override
+        public void noteVibratorOn(int uid, long duration) {
+            try {
+                if (duration <= 0) {
+                    return;
+                }
+                if (duration == Long.MAX_VALUE) {
+                    // Repeating duration has started. Report a fixed duration here, noteVibratorOff
+                    // should be called when this is cancelled.
+                    duration = BATTERY_STATS_REPEATING_VIBRATION_DURATION;
+                }
+                mBatteryStatsService.noteVibratorOn(uid, duration);
+                FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
+                        uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON,
+                        duration);
+            } catch (RemoteException e) {
+            }
+        }
+
+        @Override
+        public void noteVibratorOff(int uid) {
+            try {
+                mBatteryStatsService.noteVibratorOff(uid);
+                FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
+                        uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF,
+                        /* duration= */ 0);
+            } catch (RemoteException e) {
+            }
+        }
+
+        @Override
         public void onVibrationCompleted(long vibrationId, Vibration.Status status) {
             if (DEBUG) {
                 Slog.d(TAG, "Vibration " + vibrationId + " finished with status " + status);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 87c8a79..2da2987 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -62,8 +62,8 @@
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
-import android.graphics.BitmapRegionDecoder;
 import android.graphics.Color;
+import android.graphics.ImageDecoder;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.hardware.display.DisplayManager;
@@ -199,6 +199,8 @@
     static final String WALLPAPER_LOCK_ORIG = "wallpaper_lock_orig";
     static final String WALLPAPER_LOCK_CROP = "wallpaper_lock";
     static final String WALLPAPER_INFO = "wallpaper_info.xml";
+    private static final String RECORD_FILE = "decode_record";
+    private static final String RECORD_LOCK_FILE = "decode_lock_record";
 
     // All the various per-user state files we need to be aware of
     private static final String[] sPerUserFiles = new String[] {
@@ -689,8 +691,7 @@
                 }
 
                 if (DEBUG) {
-                    // This is just a quick estimation, may be smaller than it is.
-                    long estimateSize = options.outWidth * options.outHeight * 4;
+                    long estimateSize = (long) options.outWidth * options.outHeight * 4;
                     Slog.v(TAG, "Null crop of new wallpaper, estimate size="
                             + estimateSize + ", success=" + success);
                 }
@@ -699,9 +700,6 @@
                 FileOutputStream f = null;
                 BufferedOutputStream bos = null;
                 try {
-                    BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(
-                            wallpaper.wallpaperFile.getAbsolutePath(), false);
-
                     // This actually downsamples only by powers of two, but that's okay; we do
                     // a proper scaling blit later.  This is to minimize transient RAM use.
                     // We calculate the largest power-of-two under the actual ratio rather than
@@ -755,8 +753,24 @@
                         Slog.v(TAG, "  maxTextureSize=" + GLHelper.getMaxTextureSize());
                     }
 
-                    Bitmap cropped = decoder.decodeRegion(cropHint, options);
-                    decoder.recycle();
+                    //Create a record file and will delete if ImageDecoder work well.
+                    final String recordName =
+                            (wallpaper.wallpaperFile.getName().equals(WALLPAPER)
+                                    ? RECORD_FILE : RECORD_LOCK_FILE);
+                    final File record = new File(getWallpaperDir(wallpaper.userId), recordName);
+                    record.createNewFile();
+                    Slog.v(TAG, "record path =" + record.getPath()
+                            + ", record name =" + record.getName());
+
+                    final ImageDecoder.Source srcData =
+                            ImageDecoder.createSource(wallpaper.wallpaperFile);
+                    final int sampleSize = scale;
+                    Bitmap cropped = ImageDecoder.decodeBitmap(srcData, (decoder, info, src) -> {
+                        decoder.setTargetSampleSize(sampleSize);
+                        decoder.setCrop(estimateCrop);
+                    });
+
+                    record.delete();
 
                     if (cropped == null) {
                         Slog.e(TAG, "Could not decode new wallpaper");
@@ -1819,6 +1833,7 @@
                     new UserSwitchObserver() {
                         @Override
                         public void onUserSwitching(int newUserId, IRemoteCallback reply) {
+                            errorCheck(newUserId);
                             switchUser(newUserId, reply);
                         }
                     }, TAG);
@@ -1856,6 +1871,14 @@
 
     @Override
     public void onBootPhase(int phase) {
+        // If someone set too large jpg file as wallpaper, system_server may be killed by lmk in
+        // generateCrop(), so we create a file in generateCrop() before ImageDecoder starts working
+        // and delete this file after ImageDecoder finishing. If the specific file exists, that
+        // means ImageDecoder can't handle the original wallpaper file, in order to avoid
+        // system_server restart again and again and rescue party will trigger factory reset,
+        // so we reset default wallpaper in case system_server is trapped into a restart loop.
+        errorCheck(UserHandle.USER_SYSTEM);
+
         if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
             systemReady();
         } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
@@ -1863,6 +1886,38 @@
         }
     }
 
+    private static final HashMap<Integer, String> sWallpaperType = new HashMap<Integer, String>() {
+        {
+            put(FLAG_SYSTEM, RECORD_FILE);
+            put(FLAG_LOCK, RECORD_LOCK_FILE);
+        }
+    };
+
+    private void errorCheck(int userID) {
+        sWallpaperType.forEach((type, filename) -> {
+            final File record = new File(getWallpaperDir(userID), filename);
+            if (record.exists()) {
+                Slog.w(TAG, "User:" + userID + ", wallpaper tyep = " + type
+                        + ", wallpaper fail detect!! reset to default wallpaper");
+                clearWallpaperData(userID, type);
+                record.delete();
+            }
+        });
+    }
+
+    private void clearWallpaperData(int userID, int wallpaperType) {
+        final WallpaperData wallpaper = new WallpaperData(userID, getWallpaperDir(userID),
+                (wallpaperType == FLAG_LOCK) ? WALLPAPER_LOCK_ORIG : WALLPAPER,
+                (wallpaperType == FLAG_LOCK) ? WALLPAPER_LOCK_CROP : WALLPAPER_CROP);
+        if (wallpaper.sourceExists()) {
+            wallpaper.wallpaperFile.delete();
+        }
+        if (wallpaper.cropExists()) {
+            wallpaper.cropFile.delete();
+        }
+
+    }
+
     @Override
     public void onUnlockUser(final int userId) {
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 0396a11..8f703c5 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -45,7 +45,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowTracing.WINSCOPE_EXT;
-import static com.android.server.wm.utils.RegionUtils.forEachRect;
 
 import android.accessibilityservice.AccessibilityTrace;
 import android.animation.ObjectAnimator;
@@ -101,6 +100,7 @@
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
 import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.wm.AccessibilityWindowsPopulator.AccessibilityWindow;
 import com.android.server.wm.WindowManagerInternal.AccessibilityControllerInternal;
 import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
 import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
@@ -133,19 +133,22 @@
     private static final Rect EMPTY_RECT = new Rect();
     private static final float[] sTempFloats = new float[9];
 
-    private SparseArray<DisplayMagnifier> mDisplayMagnifiers = new SparseArray<>();
-    private SparseArray<WindowsForAccessibilityObserver> mWindowsForAccessibilityObserver =
+    private final SparseArray<DisplayMagnifier> mDisplayMagnifiers = new SparseArray<>();
+    private final SparseArray<WindowsForAccessibilityObserver> mWindowsForAccessibilityObserver =
             new SparseArray<>();
     private SparseArray<IBinder> mFocusedWindow = new SparseArray<>();
     private int mFocusedDisplay = -1;
     private boolean mIsImeVisible = false;
     // Set to true if initializing window population complete.
     private boolean mAllObserversInitialized = true;
+    private final AccessibilityWindowsPopulator mAccessibilityWindowsPopulator;
 
     AccessibilityController(WindowManagerService service) {
         mService = service;
         mAccessibilityTracing =
                 AccessibilityController.getAccessibilityControllerInternal(service);
+
+        mAccessibilityWindowsPopulator = new AccessibilityWindowsPopulator(mService, this);
     }
 
     boolean setMagnificationCallbacks(int displayId, MagnificationCallbacks callbacks) {
@@ -209,7 +212,9 @@
                 }
                 mWindowsForAccessibilityObserver.remove(displayId);
             }
-            observer = new WindowsForAccessibilityObserver(mService, displayId, callback);
+            mAccessibilityWindowsPopulator.setWindowsNotification(true);
+            observer = new WindowsForAccessibilityObserver(mService, displayId, callback,
+                    mAccessibilityWindowsPopulator);
             mWindowsForAccessibilityObserver.put(displayId, observer);
             mAllObserversInitialized &= observer.mInitialized;
         } else {
@@ -224,6 +229,10 @@
                 }
             }
             mWindowsForAccessibilityObserver.remove(displayId);
+
+            if (mWindowsForAccessibilityObserver.size() <= 0) {
+                mAccessibilityWindowsPopulator.setWindowsNotification(false);
+            }
         }
     }
 
@@ -309,11 +318,6 @@
         if (displayMagnifier != null) {
             displayMagnifier.onDisplaySizeChanged(displayContent);
         }
-        final WindowsForAccessibilityObserver windowsForA11yObserver =
-                mWindowsForAccessibilityObserver.get(displayId);
-        if (windowsForA11yObserver != null) {
-            windowsForA11yObserver.scheduleComputeChangedWindows();
-        }
     }
 
     void onAppWindowTransition(int displayId, int transition) {
@@ -341,11 +345,6 @@
         if (displayMagnifier != null) {
             displayMagnifier.onWindowTransition(windowState, transition);
         }
-        final WindowsForAccessibilityObserver windowsForA11yObserver =
-                mWindowsForAccessibilityObserver.get(displayId);
-        if (windowsForA11yObserver != null) {
-            windowsForA11yObserver.scheduleComputeChangedWindows();
-        }
     }
 
     void onWindowFocusChangedNot(int displayId) {
@@ -455,6 +454,19 @@
         return null;
     }
 
+    boolean getMagnificationSpecForDisplay(int displayId, MagnificationSpec outSpec) {
+        if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+            mAccessibilityTracing.logTrace(TAG + ".getMagnificationSpecForDisplay",
+                    FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId);
+        }
+        final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+        if (displayMagnifier == null) {
+            return false;
+        }
+
+        return displayMagnifier.getMagnificationSpec(outSpec);
+    }
+
     boolean hasCallbacks() {
         if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
                 | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
@@ -756,6 +768,25 @@
             return spec;
         }
 
+        boolean getMagnificationSpec(MagnificationSpec outSpec) {
+            if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+                mAccessibilityTracing.logTrace(LOG_TAG + ".getMagnificationSpec",
+                        FLAGS_MAGNIFICATION_CALLBACK);
+            }
+            MagnificationSpec spec = mMagnifedViewport.getMagnificationSpec();
+            if (spec == null) {
+                return false;
+            }
+
+            outSpec.setTo(spec);
+            if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+                mAccessibilityTracing.logTrace(LOG_TAG + ".getMagnificationSpec",
+                        FLAGS_MAGNIFICATION_CALLBACK, "outSpec={" + outSpec + "}");
+            }
+
+            return true;
+        }
+
         void getMagnificationRegion(Region outMagnificationRegion) {
             if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
                 mAccessibilityTracing.logTrace(LOG_TAG + ".getMagnificationRegion",
@@ -1403,20 +1434,18 @@
 
         private static final boolean DEBUG = false;
 
-        private final SparseArray<WindowState> mTempWindowStates = new SparseArray<>();
+        private final List<AccessibilityWindow> mTempA11yWindows = new ArrayList<>();
 
         private final Set<IBinder> mTempBinderSet = new ArraySet<>();
 
-        private final RectF mTempRectF = new RectF();
-
-        private final Matrix mTempMatrix = new Matrix();
-
         private final Point mTempPoint = new Point();
 
         private final Region mTempRegion = new Region();
 
         private final Region mTempRegion1 = new Region();
 
+        private final Region mTempRegion2 = new Region();
+
         private final WindowManagerService mService;
 
         private final Handler mHandler;
@@ -1431,10 +1460,11 @@
 
         // Set to true if initializing window population complete.
         private boolean mInitialized;
+        private final AccessibilityWindowsPopulator mA11yWindowsPopulator;
 
         WindowsForAccessibilityObserver(WindowManagerService windowManagerService,
-                int displayId,
-                WindowsForAccessibilityCallback callback) {
+                int displayId, WindowsForAccessibilityCallback callback,
+                AccessibilityWindowsPopulator accessibilityWindowsPopulator) {
             mService = windowManagerService;
             mCallback = callback;
             mDisplayId = displayId;
@@ -1443,6 +1473,7 @@
                     AccessibilityController.getAccessibilityControllerInternal(mService);
             mRecurringAccessibilityEventsIntervalMillis = ViewConfiguration
                     .getSendRecurringAccessibilityEventsInterval();
+            mA11yWindowsPopulator = accessibilityWindowsPopulator;
             computeChangedWindows(true);
         }
 
@@ -1466,52 +1497,6 @@
             }
         }
 
-        boolean shellRootIsAbove(WindowState windowState, ShellRoot shellRoot) {
-            int wsLayer = mService.mPolicy.getWindowLayerLw(windowState);
-            int shellLayer = mService.mPolicy.getWindowLayerFromTypeLw(shellRoot.getWindowType(),
-                    true);
-            return shellLayer >= wsLayer;
-        }
-
-        int addShellRootsIfAbove(WindowState windowState, ArrayList<ShellRoot> shellRoots,
-                int shellRootIndex, List<WindowInfo> windows, Set<IBinder> addedWindows,
-                Region unaccountedSpace, boolean focusedWindowAdded) {
-            while (shellRootIndex < shellRoots.size()
-                    && shellRootIsAbove(windowState, shellRoots.get(shellRootIndex))) {
-                ShellRoot shellRoot = shellRoots.get(shellRootIndex);
-                shellRootIndex++;
-                final WindowInfo info = shellRoot.getWindowInfo();
-                if (info == null) {
-                    continue;
-                }
-
-                info.layer = addedWindows.size();
-                windows.add(info);
-                addedWindows.add(info.token);
-                unaccountedSpace.op(info.regionInScreen, unaccountedSpace,
-                        Region.Op.REVERSE_DIFFERENCE);
-                if (unaccountedSpace.isEmpty() && focusedWindowAdded) {
-                    break;
-                }
-            }
-            return shellRootIndex;
-        }
-
-        private ArrayList<ShellRoot> getSortedShellRoots(
-                SparseArray<ShellRoot> originalShellRoots) {
-            ArrayList<ShellRoot> sortedShellRoots = new ArrayList<>(originalShellRoots.size());
-            for (int i = originalShellRoots.size() - 1; i >= 0; --i) {
-                sortedShellRoots.add(originalShellRoots.valueAt(i));
-            }
-
-            sortedShellRoots.sort((left, right) ->
-                    mService.mPolicy.getWindowLayerFromTypeLw(right.getWindowType(), true)
-                            - mService.mPolicy.getWindowLayerFromTypeLw(left.getWindowType(),
-                            true));
-
-            return sortedShellRoots;
-        }
-
         /**
          * Check if windows have changed, and send them to the accessibility subsystem if they have.
          *
@@ -1561,44 +1546,29 @@
                 Region unaccountedSpace = mTempRegion;
                 unaccountedSpace.set(0, 0, screenWidth, screenHeight);
 
-                final SparseArray<WindowState> visibleWindows = mTempWindowStates;
-                populateVisibleWindowsOnScreen(visibleWindows);
+                final List<AccessibilityWindow> visibleWindows = mTempA11yWindows;
+                mA11yWindowsPopulator.populateVisibleWindowsOnScreenLocked(
+                        mDisplayId, visibleWindows);
                 Set<IBinder> addedWindows = mTempBinderSet;
                 addedWindows.clear();
 
                 boolean focusedWindowAdded = false;
 
                 final int visibleWindowCount = visibleWindows.size();
-                ArrayList<TaskFragment> skipRemainingWindowsForTaskFragments = new ArrayList<>();
-
-                ArrayList<ShellRoot> shellRoots = getSortedShellRoots(dc.mShellRoots);
 
                 // Iterate until we figure out what is touchable for the entire screen.
-                int shellRootIndex = 0;
-                for (int i = visibleWindowCount - 1; i >= 0; i--) {
-                    final WindowState windowState = visibleWindows.valueAt(i);
-                    int prevShellRootIndex = shellRootIndex;
-                    shellRootIndex = addShellRootsIfAbove(windowState, shellRoots, shellRootIndex,
-                            windows, addedWindows, unaccountedSpace, focusedWindowAdded);
-
-                    // If a Shell Root was added, it could have accounted for all the space already.
-                    if (shellRootIndex > prevShellRootIndex && unaccountedSpace.isEmpty()
-                            && focusedWindowAdded) {
-                        break;
-                    }
-
-                    final Region regionInScreen = new Region();
-                    computeWindowRegionInScreen(windowState, regionInScreen);
-                    if (windowMattersToAccessibility(windowState,
-                            regionInScreen, unaccountedSpace,
-                            skipRemainingWindowsForTaskFragments)) {
-                        addPopulatedWindowInfo(windowState, regionInScreen, windows, addedWindows);
-                        if (windowMattersToUnaccountedSpaceComputation(windowState)) {
-                            updateUnaccountedSpace(windowState, regionInScreen, unaccountedSpace,
-                                    skipRemainingWindowsForTaskFragments);
+                for (int i = 0; i < visibleWindowCount; i++) {
+                    final AccessibilityWindow a11yWindow = visibleWindows.get(i);
+                    final Region regionInWindow = new Region();
+                    a11yWindow.getTouchableRegionInWindow(regionInWindow);
+                    if (windowMattersToAccessibility(a11yWindow, regionInWindow,
+                            unaccountedSpace)) {
+                        addPopulatedWindowInfo(a11yWindow, regionInWindow, windows, addedWindows);
+                        if (windowMattersToUnaccountedSpaceComputation(a11yWindow)) {
+                            updateUnaccountedSpace(a11yWindow, unaccountedSpace);
                         }
-                        focusedWindowAdded |= windowState.isFocused();
-                    } else if (isUntouchableNavigationBar(windowState, mTempRegion1)) {
+                        focusedWindowAdded |= a11yWindow.isFocused();
+                    } else if (a11yWindow.isUntouchableNavigationBar()) {
                         // If this widow is navigation bar without touchable region, accounting the
                         // region of navigation bar inset because all touch events from this region
                         // would be received by launcher, i.e. this region is a un-touchable one
@@ -1647,47 +1617,39 @@
 
         // Some windows should be excluded from unaccounted space computation, though they still
         // should be reported
-        private boolean windowMattersToUnaccountedSpaceComputation(WindowState windowState) {
+        private boolean windowMattersToUnaccountedSpaceComputation(AccessibilityWindow a11yWindow) {
             // Do not account space of trusted non-touchable windows, except the split-screen
             // divider.
             // If it's not trusted, touch events are not sent to the windows behind it.
-            if (((windowState.mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0)
-                    && (windowState.mAttrs.type != TYPE_DOCK_DIVIDER)
-                    && windowState.isTrustedOverlay()) {
+            if (((a11yWindow.getFlags() & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0)
+                    && (a11yWindow.getType() != TYPE_DOCK_DIVIDER)
+                    && a11yWindow.isTrustedOverlay()) {
                 return false;
             }
 
-            if (windowState.mAttrs.type
-                    == WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) {
+            if (a11yWindow.getType() == WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) {
                 return false;
             }
             return true;
         }
 
-        private boolean windowMattersToAccessibility(WindowState windowState,
-                Region regionInScreen, Region unaccountedSpace,
-                ArrayList<TaskFragment> skipRemainingWindowsForTaskFragments) {
-            final RecentsAnimationController controller = mService.getRecentsAnimationController();
-            if (controller != null && controller.shouldIgnoreForAccessibility(windowState)) {
+        private boolean windowMattersToAccessibility(AccessibilityWindow a11yWindow,
+                Region regionInScreen, Region unaccountedSpace) {
+            if (a11yWindow.ignoreRecentsAnimationForAccessibility()) {
                 return false;
             }
 
-            if (windowState.isFocused()) {
+            if (a11yWindow.isFocused()) {
                 return true;
             }
 
-            // If the window is part of a task that we're finished with - ignore.
-            final TaskFragment taskFragment = windowState.getTaskFragment();
-            if (taskFragment != null
-                    && skipRemainingWindowsForTaskFragments.contains(taskFragment)) {
-                return false;
-            }
-
             // Ignore non-touchable windows, except the split-screen divider, which is
             // occasionally non-touchable but still useful for identifying split-screen
-            // mode.
-            if (((windowState.mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0)
-                    && (windowState.mAttrs.type != TYPE_DOCK_DIVIDER)) {
+            // mode and the PIP menu.
+            if (((a11yWindow.getFlags()
+                    & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0)
+                    && (a11yWindow.getType() != TYPE_DOCK_DIVIDER
+                    && !a11yWindow.isPIPMenu())) {
                 return false;
             }
 
@@ -1697,88 +1659,36 @@
             }
 
             // Add windows of certain types not covered by modal windows.
-            if (isReportedWindowType(windowState.mAttrs.type)) {
+            if (isReportedWindowType(a11yWindow.getType())) {
                 return true;
             }
 
             return false;
         }
 
-        private void updateUnaccountedSpace(WindowState windowState, Region regionInScreen,
-                Region unaccountedSpace,
-                ArrayList<TaskFragment> skipRemainingWindowsForTaskFragments) {
-            // Account for the space this window takes if the window
-            // is not an accessibility overlay which does not change
-            // the reported windows.
-            unaccountedSpace.op(regionInScreen, unaccountedSpace,
-                    Region.Op.REVERSE_DIFFERENCE);
-
-            // If a window is modal it prevents other windows from being touched
-            if ((windowState.mAttrs.flags & (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)) == 0) {
-                if (!windowState.hasTapExcludeRegion()) {
-                    // Account for all space in the task, whether the windows in it are
-                    // touchable or not. The modal window blocks all touches from the task's
-                    // area.
-                    unaccountedSpace.op(windowState.getDisplayFrame(), unaccountedSpace,
-                            Region.Op.REVERSE_DIFFERENCE);
-                } else {
-                    // If a window has tap exclude region, we need to account it.
-                    final Region displayRegion = new Region(windowState.getDisplayFrame());
-                    final Region tapExcludeRegion = new Region();
-                    windowState.getTapExcludeRegion(tapExcludeRegion);
-                    displayRegion.op(tapExcludeRegion, displayRegion,
-                            Region.Op.REVERSE_DIFFERENCE);
-                    unaccountedSpace.op(displayRegion, unaccountedSpace,
-                            Region.Op.REVERSE_DIFFERENCE);
-                }
-
-                final TaskFragment taskFragment = windowState.getTaskFragment();
-                if (taskFragment != null) {
-                    // If the window is associated with a particular task, we can skip the
-                    // rest of the windows for that task.
-                    skipRemainingWindowsForTaskFragments.add(taskFragment);
-                } else if (!windowState.hasTapExcludeRegion()) {
-                    // If the window is not associated with a particular task, then it is
-                    // globally modal. In this case we can skip all remaining windows when
-                    // it doesn't has tap exclude region.
-                    unaccountedSpace.setEmpty();
-                }
-            }
-
-            // Account for the space of letterbox.
-            if (windowState.areAppWindowBoundsLetterboxed()) {
-                unaccountedSpace.op(getLetterboxBounds(windowState), unaccountedSpace,
+        private void updateUnaccountedSpace(AccessibilityWindow a11yWindow,
+                Region unaccountedSpace) {
+            if (a11yWindow.getType()
+                    != WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) {
+                // Account for the space this window takes if the window
+                // is not an accessibility overlay which does not change
+                // the reported windows.
+                final Region touchableRegion = mTempRegion2;
+                a11yWindow.getTouchableRegionInScreen(touchableRegion);
+                unaccountedSpace.op(touchableRegion, unaccountedSpace,
                         Region.Op.REVERSE_DIFFERENCE);
+                // Account for the space of letterbox.
+                final Region letterboxBounds = mTempRegion1;
+                if (a11yWindow.setLetterBoxBoundsIfNeeded(letterboxBounds)) {
+                    unaccountedSpace.op(letterboxBounds,
+                            unaccountedSpace, Region.Op.REVERSE_DIFFERENCE);
+                }
             }
         }
 
-        private void computeWindowRegionInScreen(WindowState windowState, Region outRegion) {
-            // Get the touchable frame.
-            Region touchableRegion = mTempRegion1;
-            windowState.getTouchableRegion(touchableRegion);
-
-            // Map the frame to get what appears on the screen.
-            Matrix matrix = mTempMatrix;
-            populateTransformationMatrix(windowState, matrix);
-
-            forEachRect(touchableRegion, rect -> {
-                // Move to origin as all transforms are captured by the matrix.
-                RectF windowFrame = mTempRectF;
-                windowFrame.set(rect);
-                windowFrame.offset(-windowState.getFrame().left, -windowState.getFrame().top);
-
-                matrix.mapRect(windowFrame);
-
-                // Union all rects.
-                outRegion.union(new Rect((int) windowFrame.left, (int) windowFrame.top,
-                        (int) windowFrame.right, (int) windowFrame.bottom));
-            });
-        }
-
-        private static void addPopulatedWindowInfo(WindowState windowState, Region regionInScreen,
-                List<WindowInfo> out, Set<IBinder> tokenOut) {
-            final WindowInfo window = windowState.getWindowInfo();
+        private static void addPopulatedWindowInfo(AccessibilityWindow a11yWindow,
+                Region regionInScreen, List<WindowInfo> out, Set<IBinder> tokenOut) {
+            final WindowInfo window = a11yWindow.getWindowInfo();
             window.regionInScreen.set(regionInScreen);
             window.layer = tokenOut.size();
             out.add(window);
@@ -1805,23 +1715,6 @@
                     && windowType != WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
         }
 
-        private void populateVisibleWindowsOnScreen(SparseArray<WindowState> outWindows) {
-            final List<WindowState> tempWindowStatesList = new ArrayList<>();
-            final DisplayContent dc = mService.mRoot.getDisplayContent(mDisplayId);
-            if (dc == null) {
-                return;
-            }
-
-            dc.forAllWindows(w -> {
-                if (w.isVisible()) {
-                    tempWindowStatesList.add(w);
-                }
-            }, false /* traverseTopToBottom */);
-            for (int i = 0; i < tempWindowStatesList.size(); i++) {
-                outWindows.put(i, tempWindowStatesList.get(i));
-            }
-        }
-
         private WindowState getTopFocusWindow() {
             return mService.mRoot.getTopFocusedDisplayContent().mCurrentFocus;
         }
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
new file mode 100644
index 0000000..c0fb83b
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -0,0 +1,626 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+
+import static com.android.server.wm.utils.RegionUtils.forEachRect;
+
+import android.annotation.NonNull;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.IWindow;
+import android.view.InputWindowHandle;
+import android.view.MagnificationSpec;
+import android.view.WindowInfo;
+import android.view.WindowManager;
+import android.window.WindowInfosListener;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class is the accessibility windows population adapter.
+ */
+public final class AccessibilityWindowsPopulator extends WindowInfosListener {
+
+    private static final String TAG = AccessibilityWindowsPopulator.class.getSimpleName();
+    // If the surface flinger callback is not coming within in 2 frames time, i.e. about
+    // 35ms, then assuming the windows become stable.
+    private static final int SURFACE_FLINGER_CALLBACK_WINDOWS_STABLE_TIMES_MS = 35;
+    // To avoid the surface flinger callbacks always comes within in 2 frames, then no windows
+    // are reported to the A11y framework, and the animation duration time is 500ms, so setting
+    // this value as the max timeout value to force computing changed windows.
+    private static final int WINDOWS_CHANGED_NOTIFICATION_MAX_DURATION_TIMES_MS = 500;
+
+    private static final float[] sTempFloats = new float[9];
+
+    private final WindowManagerService mService;
+    private final AccessibilityController mAccessibilityController;
+    @GuardedBy("mLock")
+    private final SparseArray<List<InputWindowHandle>> mInputWindowHandlesOnDisplays =
+            new SparseArray<>();
+    @GuardedBy("mLock")
+    private final SparseArray<Matrix> mMagnificationSpecInverseMatrix = new SparseArray<>();
+    @GuardedBy("mLock")
+    private final SparseArray<DisplayInfo> mDisplayInfos = new SparseArray<>();
+    @GuardedBy("mLock")
+    private final List<InputWindowHandle> mVisibleWindows = new ArrayList<>();
+    @GuardedBy("mLock")
+    private boolean mWindowsNotificationEnabled = false;
+    private final Object mLock = new Object();
+    private final Handler mHandler;
+
+    AccessibilityWindowsPopulator(WindowManagerService service,
+            AccessibilityController accessibilityController) {
+        mService = service;
+        mAccessibilityController = accessibilityController;
+        mHandler = new MyHandler(mService.mH.getLooper());
+
+        register();
+    }
+
+    /**
+     * Gets the visible windows list with the window layer on the specified display.
+     *
+     * @param displayId The display.
+     * @param outWindows The visible windows list. The z-order of each window in the list
+     *                   is from the top to bottom.
+     */
+    public void populateVisibleWindowsOnScreenLocked(int displayId,
+            List<AccessibilityWindow> outWindows) {
+        List<InputWindowHandle> inputWindowHandles;
+        final Matrix inverseMatrix = new Matrix();
+        final Matrix displayMatrix = new Matrix();
+
+        synchronized (mLock) {
+            inputWindowHandles = mInputWindowHandlesOnDisplays.get(displayId);
+            if (inputWindowHandles == null) {
+                outWindows.clear();
+
+                return;
+            }
+            inverseMatrix.set(mMagnificationSpecInverseMatrix.get(displayId));
+
+            final DisplayInfo displayInfo = mDisplayInfos.get(displayId);
+            if (displayInfo != null) {
+                displayMatrix.set(displayInfo.mTransform);
+            } else {
+                Slog.w(TAG, "The displayInfo of this displayId (" + displayId + ") called "
+                        + "back from the surface fligner is null");
+            }
+        }
+
+        final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
+        final ShellRoot shellroot = dc.mShellRoots.get(WindowManager.SHELL_ROOT_LAYER_PIP);
+        final IBinder pipMenuIBinder =
+                shellroot != null ? shellroot.getAccessibilityWindowToken() : null;
+
+        for (final InputWindowHandle windowHandle : inputWindowHandles) {
+            final AccessibilityWindow accessibilityWindow =
+                    AccessibilityWindow.initializeData(mService, windowHandle, inverseMatrix,
+                            pipMenuIBinder, displayMatrix);
+
+            outWindows.add(accessibilityWindow);
+        }
+    }
+
+    @Override
+    public void onWindowInfosChanged(InputWindowHandle[] windowHandles,
+            DisplayInfo[] displayInfos) {
+        synchronized (mLock) {
+            mVisibleWindows.clear();
+            for (InputWindowHandle window : windowHandles) {
+                if (window.visible && window.getWindow() != null) {
+                    mVisibleWindows.add(window);
+                }
+            }
+
+            mDisplayInfos.clear();
+            for (final DisplayInfo displayInfo : displayInfos) {
+                mDisplayInfos.put(displayInfo.mDisplayId, displayInfo);
+            }
+
+            if (mWindowsNotificationEnabled) {
+                if (!mHandler.hasMessages(
+                        MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_TIMEOUT)) {
+                    mHandler.sendEmptyMessageDelayed(
+                            MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_TIMEOUT,
+                            WINDOWS_CHANGED_NOTIFICATION_MAX_DURATION_TIMES_MS);
+                }
+                populateVisibleWindowHandlesAndNotifyWindowsChangeIfNeededLocked();
+            }
+        }
+    }
+
+    /**
+     * Sets to notify the accessibilityController to compute changed windows on
+     * the display after populating the visible windows if the windows reported
+     * from the surface flinger changes.
+     *
+     * @param register {@code true} means starting windows population.
+     */
+    public void setWindowsNotification(boolean register) {
+        synchronized (mLock) {
+            if (mWindowsNotificationEnabled == register) {
+                return;
+            }
+            mWindowsNotificationEnabled = register;
+            if (mWindowsNotificationEnabled) {
+                populateVisibleWindowHandlesAndNotifyWindowsChangeIfNeededLocked();
+            } else {
+                releaseResources();
+            }
+        }
+    }
+
+    private void populateVisibleWindowHandlesAndNotifyWindowsChangeIfNeededLocked() {
+        final SparseArray<List<InputWindowHandle>> tempWindowHandleList = new SparseArray<>();
+
+        for (final InputWindowHandle windowHandle : mVisibleWindows) {
+            List<InputWindowHandle> inputWindowHandles = tempWindowHandleList.get(
+                    windowHandle.displayId);
+
+            if (inputWindowHandles == null) {
+                inputWindowHandles = new ArrayList<>();
+                tempWindowHandleList.put(windowHandle.displayId, inputWindowHandles);
+                generateMagnificationSpecInverseMatrixLocked(windowHandle.displayId);
+            }
+            inputWindowHandles.add(windowHandle);
+        }
+
+        final List<Integer> displayIdsForWindowsChanged = new ArrayList<>();
+
+        getDisplaysForWindowsChangedLocked(displayIdsForWindowsChanged, tempWindowHandleList,
+                mInputWindowHandlesOnDisplays);
+        // Clones all windows from the callback of the surface flinger.
+        mInputWindowHandlesOnDisplays.clear();
+        for (int i = 0; i < tempWindowHandleList.size(); i++) {
+            final int displayId = tempWindowHandleList.keyAt(i);
+            mInputWindowHandlesOnDisplays.put(displayId, tempWindowHandleList.get(displayId));
+        }
+
+        if (displayIdsForWindowsChanged.size() > 0) {
+            if (!mHandler.hasMessages(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED)) {
+                mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED,
+                        displayIdsForWindowsChanged).sendToTarget();
+            }
+
+            return;
+        }
+        mHandler.removeMessages(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_UI_STABLE);
+        mHandler.sendEmptyMessageDelayed(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_UI_STABLE,
+                SURFACE_FLINGER_CALLBACK_WINDOWS_STABLE_TIMES_MS);
+    }
+
+    private void getDisplaysForWindowsChangedLocked(List<Integer> outDisplayIdsForWindowsChanged,
+            SparseArray<List<InputWindowHandle>> newWindowsList,
+            SparseArray<List<InputWindowHandle>> oldWindowsList) {
+        for (int i = 0; i < newWindowsList.size(); i++) {
+            final int displayId = newWindowsList.keyAt(i);
+            final List<InputWindowHandle> newWindows = newWindowsList.get(displayId);
+            final List<InputWindowHandle> oldWindows = oldWindowsList.get(displayId);
+
+            if (hasWindowsChangedLocked(newWindows, oldWindows)) {
+                outDisplayIdsForWindowsChanged.add(displayId);
+            }
+        }
+    }
+
+    private boolean hasWindowsChangedLocked(List<InputWindowHandle> newWindows,
+            List<InputWindowHandle> oldWindows) {
+        if (oldWindows == null || oldWindows.size() != newWindows.size()) {
+            return true;
+        }
+
+        final int windowsCount = newWindows.size();
+        // Since we always traverse windows from high to low layer,
+        // the old and new windows at the same index should be the
+        // same, otherwise something changed.
+        for (int i = 0; i < windowsCount; i++) {
+            final InputWindowHandle newWindow = newWindows.get(i);
+            final InputWindowHandle oldWindow = oldWindows.get(i);
+
+            if (!newWindow.getWindow().asBinder().equals(oldWindow.getWindow().asBinder())) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private void generateMagnificationSpecInverseMatrixLocked(int displayId) {
+        MagnificationSpec spec = new MagnificationSpec();
+        if (!mAccessibilityController.getMagnificationSpecForDisplay(displayId, spec)) {
+            mMagnificationSpecInverseMatrix.remove(displayId);
+            return;
+        }
+        sTempFloats[Matrix.MSCALE_X] = spec.scale;
+        sTempFloats[Matrix.MSKEW_Y] = 0;
+        sTempFloats[Matrix.MSKEW_X] = 0;
+        sTempFloats[Matrix.MSCALE_Y] = spec.scale;
+        sTempFloats[Matrix.MTRANS_X] = spec.offsetX;
+        sTempFloats[Matrix.MTRANS_Y] = spec.offsetY;
+        sTempFloats[Matrix.MPERSP_0] = 0;
+        sTempFloats[Matrix.MPERSP_1] = 0;
+        sTempFloats[Matrix.MPERSP_2] = 1;
+
+        final Matrix tempMatrix = new Matrix();
+        tempMatrix.setValues(sTempFloats);
+
+        final Matrix inverseMatrix = new Matrix();
+        final boolean result = tempMatrix.invert(inverseMatrix);
+
+        if (!result) {
+            Slog.e(TAG, "Can't inverse the magnification spec matrix with the "
+                    + "magnification spec = " + spec + " on the displayId = " + displayId);
+            return;
+        }
+        mMagnificationSpecInverseMatrix.set(displayId, inverseMatrix);
+    }
+
+    private void notifyWindowsChanged(@NonNull List<Integer> displayIdsForWindowsChanged) {
+        mHandler.removeMessages(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_TIMEOUT);
+
+        for (int i = 0; i < displayIdsForWindowsChanged.size(); i++) {
+            mAccessibilityController.performComputeChangedWindowsNot(
+                    displayIdsForWindowsChanged.get(i), false);
+        }
+    }
+
+    private void forceUpdateWindows() {
+        final List<Integer> displayIdsForWindowsChanged = new ArrayList<>();
+
+        synchronized (mLock) {
+            for (int i = 0; i < mInputWindowHandlesOnDisplays.size(); i++) {
+                final int displayId = mInputWindowHandlesOnDisplays.keyAt(i);
+                displayIdsForWindowsChanged.add(displayId);
+            }
+        }
+        notifyWindowsChanged(displayIdsForWindowsChanged);
+    }
+
+    @GuardedBy("mLock")
+    private void releaseResources() {
+        mInputWindowHandlesOnDisplays.clear();
+        mMagnificationSpecInverseMatrix.clear();
+        mVisibleWindows.clear();
+        mDisplayInfos.clear();
+        mWindowsNotificationEnabled = false;
+        mHandler.removeCallbacksAndMessages(null);
+    }
+
+    private class MyHandler extends Handler {
+        public static final int MESSAGE_NOTIFY_WINDOWS_CHANGED = 1;
+        public static final int MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_UI_STABLE = 2;
+        public static final int MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_TIMEOUT = 3;
+
+        MyHandler(Looper looper) {
+            super(looper, null, false);
+        }
+
+        @Override
+        public void handleMessage(Message message) {
+            switch (message.what) {
+                case MESSAGE_NOTIFY_WINDOWS_CHANGED: {
+                    final List<Integer> displayIdsForWindowsChanged = (List<Integer>) message.obj;
+                    notifyWindowsChanged(displayIdsForWindowsChanged);
+                } break;
+
+                case MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_UI_STABLE: {
+                    forceUpdateWindows();
+                } break;
+
+                case MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_TIMEOUT: {
+                    Slog.w(TAG, "Windows change within in 2 frames continuously over 500 ms "
+                            + "and notify windows changed immediately");
+                    mHandler.removeMessages(
+                            MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED_BY_UI_STABLE);
+
+                    forceUpdateWindows();
+                } break;
+            }
+        }
+    }
+
+    /**
+     * This class represents information about a window from the
+     * surface flinger to the accessibility framework.
+     */
+    public static class AccessibilityWindow {
+        private static final Region TEMP_REGION = new Region();
+        private static final RectF TEMP_RECTF = new RectF();
+        // Data
+        private IWindow mWindow;
+        private int mDisplayId;
+        private int mFlags;
+        private int mType;
+        private int mPrivateFlags;
+        private boolean mIsPIPMenu;
+        private boolean mIsFocused;
+        private boolean mShouldMagnify;
+        private boolean mIgnoreDuetoRecentsAnimation;
+        private boolean mIsTrustedOverlay;
+        private final Region mTouchableRegionInScreen = new Region();
+        private final Region mTouchableRegionInWindow = new Region();
+        private final Region mLetterBoxBounds = new Region();
+        private WindowInfo mWindowInfo;
+
+        /**
+         * Returns the instance after initializing the internal data.
+         * @param service The window manager service.
+         * @param inputWindowHandle The window from the surface flinger.
+         * @param inverseMatrix The magnification spec inverse matrix.
+         */
+        public static AccessibilityWindow initializeData(WindowManagerService service,
+                InputWindowHandle inputWindowHandle, Matrix inverseMatrix, IBinder pipIBinder,
+                Matrix displayMatrix) {
+            final IWindow window = inputWindowHandle.getWindow();
+            final WindowState windowState = window != null ? service.mWindowMap.get(
+                    window.asBinder()) : null;
+
+            final AccessibilityWindow instance = new AccessibilityWindow();
+
+            instance.mWindow = inputWindowHandle.getWindow();
+            instance.mDisplayId = inputWindowHandle.displayId;
+            instance.mFlags = inputWindowHandle.layoutParamsFlags;
+            instance.mType = inputWindowHandle.layoutParamsType;
+            instance.mIsPIPMenu = inputWindowHandle.getWindow().asBinder().equals(pipIBinder);
+
+            // TODO (b/199357848): gets the private flag of the window from other way.
+            instance.mPrivateFlags = windowState != null ? windowState.mAttrs.privateFlags : 0;
+            // TODO (b/199358208) : using new way to implement the focused window.
+            instance.mIsFocused = windowState != null && windowState.isFocused();
+            instance.mShouldMagnify = windowState == null || windowState.shouldMagnify();
+
+            final RecentsAnimationController controller = service.getRecentsAnimationController();
+            instance.mIgnoreDuetoRecentsAnimation = windowState != null && controller != null
+                    && controller.shouldIgnoreForAccessibility(windowState);
+            instance.mIsTrustedOverlay = inputWindowHandle.trustedOverlay;
+
+            // TODO (b/199358388) : gets the letterbox bounds of the window from other way.
+            if (windowState != null && windowState.areAppWindowBoundsLetterboxed()) {
+                getLetterBoxBounds(windowState, instance.mLetterBoxBounds);
+            }
+
+            final Rect windowFrame = new Rect(inputWindowHandle.frameLeft,
+                    inputWindowHandle.frameTop, inputWindowHandle.frameRight,
+                    inputWindowHandle.frameBottom);
+            getTouchableRegionInWindow(instance.mShouldMagnify, inputWindowHandle.touchableRegion,
+                    instance.mTouchableRegionInWindow, windowFrame, inverseMatrix, displayMatrix);
+            getUnMagnifiedTouchableRegion(instance.mShouldMagnify,
+                    inputWindowHandle.touchableRegion, instance.mTouchableRegionInScreen,
+                    inverseMatrix, displayMatrix);
+            instance.mWindowInfo = windowState != null
+                    ? windowState.getWindowInfo() : getWindowInfoForWindowlessWindows(instance);
+
+            return instance;
+        }
+
+        /**
+         * Returns the touchable region in the screen.
+         * @param outRegion The touchable region.
+         */
+        public void getTouchableRegionInScreen(Region outRegion) {
+            outRegion.set(mTouchableRegionInScreen);
+        }
+
+        /**
+         * Returns the touchable region in the window.
+         * @param outRegion The touchable region.
+         */
+        public void getTouchableRegionInWindow(Region outRegion) {
+            outRegion.set(mTouchableRegionInWindow);
+        }
+
+        /**
+         * @return the layout parameter flag {@link android.view.WindowManager.LayoutParams#flags}.
+         */
+        public int getFlags() {
+            return mFlags;
+        }
+
+        /**
+         * @return the layout parameter type {@link android.view.WindowManager.LayoutParams#type}.
+         */
+        public int getType() {
+            return mType;
+        }
+
+        /**
+         * @return the layout parameter private flag
+         * {@link android.view.WindowManager.LayoutParams#privateFlags}.
+         */
+        public int getPrivateFlag() {
+            return mPrivateFlags;
+        }
+
+        /**
+         * @return the windowInfo {@link WindowInfo}.
+         */
+        public WindowInfo getWindowInfo() {
+            return mWindowInfo;
+        }
+
+        /**
+         * Gets the letter box bounds if activity bounds are letterboxed
+         * or letterboxed for display cutout.
+         *
+         * @return {@code true} there's a letter box bounds.
+         */
+        public Boolean setLetterBoxBoundsIfNeeded(Region outBounds) {
+            if (mLetterBoxBounds.isEmpty()) {
+                return false;
+            }
+
+            outBounds.set(mLetterBoxBounds);
+            return true;
+        }
+
+        /**
+         * @return true if this window should be magnified.
+         */
+        public boolean shouldMagnify() {
+            return mShouldMagnify;
+        }
+
+        /**
+         * @return true if this window is focused.
+         */
+        public boolean isFocused() {
+            return mIsFocused;
+        }
+
+        /**
+         * @return true if it's running the recent animation but not the target app.
+         */
+        public boolean ignoreRecentsAnimationForAccessibility() {
+            return mIgnoreDuetoRecentsAnimation;
+        }
+
+        /**
+         * @return true if this window is the trusted overlay.
+         */
+        public boolean isTrustedOverlay() {
+            return mIsTrustedOverlay;
+        }
+
+        /**
+         * @return true if this window is the navigation bar with the gesture mode.
+         */
+        public boolean isUntouchableNavigationBar() {
+            if (mType != WindowManager.LayoutParams.TYPE_NAVIGATION_BAR) {
+                return false;
+            }
+
+            return mTouchableRegionInScreen.isEmpty();
+        }
+
+        /**
+         * @return true if this window is PIP menu.
+         */
+        public boolean isPIPMenu() {
+            return mIsPIPMenu;
+        }
+
+        private static void getTouchableRegionInWindow(boolean shouldMagnify, Region inRegion,
+                Region outRegion, Rect frame, Matrix inverseMatrix, Matrix displayMatrix) {
+            // Some modal windows, like the activity with Theme.dialog, has the full screen
+            // as its touchable region, but its window frame is smaller than the touchable
+            // region. The region we report should be the touchable area in the window frame
+            // for the consistency and match developers expectation.
+            // So we need to make the intersection between the frame and touchable region to
+            // obtain the real touch region in the screen.
+            Region touchRegion = TEMP_REGION;
+            touchRegion.set(inRegion);
+            touchRegion.op(frame, Region.Op.INTERSECT);
+
+            getUnMagnifiedTouchableRegion(shouldMagnify, touchRegion, outRegion, inverseMatrix,
+                    displayMatrix);
+        }
+
+        /**
+         * Gets the un-magnified touchable region. If this window can be magnified and magnifying,
+         * we will transform the input touchable region by applying the inverse matrix of the
+         * magnification spec to get the un-magnified touchable region.
+         * @param shouldMagnify The window can be magnified.
+         * @param inRegion The touchable region of this window.
+         * @param outRegion The un-magnified touchable region of this window.
+         * @param inverseMatrix The inverse matrix of the magnification spec.
+         * @param displayMatrix The display transform matrix which takes display coordinates to
+         *                      logical display coordinates.
+         */
+        private static void getUnMagnifiedTouchableRegion(boolean shouldMagnify, Region inRegion,
+                Region outRegion, Matrix inverseMatrix, Matrix displayMatrix) {
+            if ((!shouldMagnify || inverseMatrix.isIdentity()) && displayMatrix.isIdentity()) {
+                outRegion.set(inRegion);
+                return;
+            }
+
+            forEachRect(inRegion, rect -> {
+                // Move to origin as all transforms are captured by the matrix.
+                RectF windowFrame = TEMP_RECTF;
+                windowFrame.set(rect);
+
+                inverseMatrix.mapRect(windowFrame);
+                displayMatrix.mapRect(windowFrame);
+                // Union all rects.
+                outRegion.union(new Rect((int) windowFrame.left, (int) windowFrame.top,
+                        (int) windowFrame.right, (int) windowFrame.bottom));
+            });
+        }
+
+        private static WindowInfo getWindowInfoForWindowlessWindows(AccessibilityWindow window) {
+            WindowInfo windowInfo = WindowInfo.obtain();
+            windowInfo.displayId = window.mDisplayId;
+            windowInfo.type = window.mType;
+            windowInfo.token = window.mWindow.asBinder();
+            windowInfo.hasFlagWatchOutsideTouch = (window.mFlags
+                    & WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH) != 0;
+            windowInfo.inPictureInPicture = false;
+
+            // There only are two windowless windows now, one is split window, and the other
+            // one is PIP.
+            if (windowInfo.type == TYPE_DOCK_DIVIDER) {
+                windowInfo.title = "Splitscreen Divider";
+            } else if (window.mIsPIPMenu) {
+                windowInfo.title = "Picture-in-Picture menu";
+            }
+            return windowInfo;
+        }
+
+        private static void getLetterBoxBounds(WindowState windowState, Region outRegion) {
+            final Rect letterboxInsets = windowState.mActivityRecord.getLetterboxInsets();
+            final Rect nonLetterboxRect = windowState.getBounds();
+
+            nonLetterboxRect.inset(letterboxInsets);
+            outRegion.set(windowState.getBounds());
+            outRegion.op(nonLetterboxRect, Region.Op.DIFFERENCE);
+        }
+
+        @Override
+        public String toString() {
+            String builder = "A11yWindow=[" + mWindow.asBinder()
+                    + ", displayId=" + mDisplayId
+                    + ", flag=0x" + Integer.toHexString(mFlags)
+                    + ", type=" + mType
+                    + ", privateFlag=0x" + Integer.toHexString(mPrivateFlags)
+                    + ", focused=" + mIsFocused
+                    + ", shouldMagnify=" + mShouldMagnify
+                    + ", ignoreDuetoRecentsAnimation=" + mIgnoreDuetoRecentsAnimation
+                    + ", isTrustedOverlay=" + mIsTrustedOverlay
+                    + ", regionInScreen=" + mTouchableRegionInScreen
+                    + ", touchableRegion=" + mTouchableRegionInWindow
+                    + ", letterBoxBounds=" + mLetterBoxBounds
+                    + ", isPIPMenu=" + mIsPIPMenu
+                    + ", windowInfo=" + mWindowInfo
+                    + "]";
+
+            return builder;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b0efa5b..d772586 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -148,6 +148,7 @@
 import static com.android.server.wm.ActivityRecordProto.APP_STOPPED;
 import static com.android.server.wm.ActivityRecordProto.CLIENT_VISIBLE;
 import static com.android.server.wm.ActivityRecordProto.DEFER_HIDING_CLIENT;
+import static com.android.server.wm.ActivityRecordProto.ENABLE_RECENTS_SCREENSHOT;
 import static com.android.server.wm.ActivityRecordProto.FILLS_PARENT;
 import static com.android.server.wm.ActivityRecordProto.FRONT_OF_TASK;
 import static com.android.server.wm.ActivityRecordProto.IN_SIZE_COMPAT_MODE;
@@ -771,7 +772,7 @@
     // Last visibility state we reported to the app token.
     boolean reportedVisible;
 
-    boolean mEnablePreviewScreenshots = true;
+    boolean mEnableRecentsScreenshot = true;
 
     // Information about an application starting window if displayed.
     // Note: these are de-referenced before the starting window animates away.
@@ -5151,7 +5152,7 @@
      * See {@link Activity#setRecentsScreenshotEnabled}.
      */
     void setRecentsScreenshotEnabled(boolean enabled) {
-        mEnablePreviewScreenshots = enabled;
+        mEnableRecentsScreenshot = enabled;
     }
 
     /**
@@ -5163,7 +5164,7 @@
      *         screenshot.
      */
     boolean shouldUseAppThemeSnapshot() {
-        return !mEnablePreviewScreenshots || forAllWindows(WindowState::isSecureLocked,
+        return !mEnableRecentsScreenshot || forAllWindows(WindowState::isSecureLocked,
                 true /* topToBottom */);
     }
 
@@ -9184,6 +9185,7 @@
         // Only record if max bounds sandboxing is applied, if the caller has the necessary
         // permission to access the device configs.
         proto.write(PROVIDES_MAX_BOUNDS, providesMaxBounds());
+        proto.write(ENABLE_RECENTS_SCREENSHOT, mEnableRecentsScreenshot);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index ef0ee12..47bec30 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2196,8 +2196,9 @@
             // removed from calling performClearTaskLocked (For example, if it is being brought out
             // of history or if it is finished immediately), thus disassociating the task. Also note
             // that mReuseTask is reset as a result of {@link Task#performClearTaskLocked}
-            // launching another activity.
-            targetTask.performClearTaskLocked();
+            // launching another activity. Keep the task-overlay activity because the targetTask
+            // will be reused to launch new activity.
+            targetTask.performClearTaskForReuse(true /* excludingTaskOverlay*/);
             targetTask.setIntent(mStartActivity);
             mAddingToTask = true;
         } else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
@@ -2207,8 +2208,7 @@
             // In this situation we want to remove all activities from the task up to the one
             // being started. In most cases this means we are resetting the task to its initial
             // state.
-            final ActivityRecord top = targetTask.performClearTaskForReuseLocked(mStartActivity,
-                    mLaunchFlags);
+            final ActivityRecord top = targetTask.performClearTop(mStartActivity, mLaunchFlags);
 
             if (top != null) {
                 if (top.isRootOfTask()) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 25c4d20..cecfccd 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -471,7 +471,7 @@
     /** Dump the current activities state. */
     public abstract boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name,
             String[] args, int opti, boolean dumpAll, boolean dumpVisibleRootTasksOnly,
-            boolean dumpFocusedRootTaskOnly);
+            boolean dumpFocusedRootTaskOnly, @UserIdInt int userId);
 
     /** Dump the current state for inclusion in oom dump. */
     public abstract void dumpForOom(PrintWriter pw);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 0497477..fe4eae91 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4062,12 +4062,12 @@
      */
     protected boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args,
             int opti, boolean dumpAll, boolean dumpVisibleRootTasksOnly,
-            boolean dumpFocusedRootTaskOnly) {
+            boolean dumpFocusedRootTaskOnly, @UserIdInt int userId) {
         ArrayList<ActivityRecord> activities;
 
         synchronized (mGlobalLock) {
             activities = mRootWindowContainer.getDumpActivities(name, dumpVisibleRootTasksOnly,
-                    dumpFocusedRootTaskOnly);
+                    dumpFocusedRootTaskOnly, userId);
         }
 
         if (activities.size() <= 0) {
@@ -6410,9 +6410,9 @@
         @Override
         public boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name,
                 String[] args, int opti, boolean dumpAll, boolean dumpVisibleRootTasksOnly,
-                boolean dumpFocusedRootTaskOnly) {
+                boolean dumpFocusedRootTaskOnly, @UserIdInt int userId) {
             return ActivityTaskManagerService.this.dumpActivity(fd, pw, name, args, opti, dumpAll,
-                    dumpVisibleRootTasksOnly, dumpFocusedRootTaskOnly);
+                    dumpVisibleRootTasksOnly, dumpFocusedRootTaskOnly, userId);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 3d7dead..5573f16 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1605,7 +1605,7 @@
         task.mTransitionController.requestCloseTransitionIfNeeded(task);
         task.mInRemoveTask = true;
         try {
-            task.performClearTask(reason);
+            task.removeActivities(reason, false /* excludingTaskOverlay */);
             cleanUpRemovedTaskLocked(task, killProcess, removeFromRecents);
             mService.getLockTaskController().clearLockedTask(task);
             mService.getTaskChangeNotificationController().notifyTaskStackChanged();
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index 3d54b27..6befefd 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -21,6 +21,7 @@
 import static com.android.server.wm.ActivityRecord.INVALID_PID;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
+import android.annotation.NonNull;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.Process;
@@ -35,6 +36,7 @@
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.OptionalInt;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -75,7 +77,33 @@
         activity.inputDispatchingTimedOut(reason, INVALID_PID);
     }
 
-    void notifyWindowUnresponsive(IBinder inputToken, String reason) {
+
+    /**
+     * Notify a window was unresponsive.
+     *
+     * @param token - the input token of the window
+     * @param pid - the pid of the window, if known
+     * @param reason - the reason for the window being unresponsive
+     */
+    void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid,
+            @NonNull String reason) {
+        if (notifyWindowUnresponsive(token, reason)) {
+            return;
+        }
+        if (!pid.isPresent()) {
+            Slog.w(TAG_WM, "Failed to notify that window token=" + token + " was unresponsive.");
+            return;
+        }
+        notifyWindowUnresponsive(pid.getAsInt(), reason);
+    }
+
+    /**
+     * Notify a window identified by its input token was unresponsive.
+     *
+     * @return true if the window was identified by the given input token and the request was
+     *         handled, false otherwise.
+     */
+    private boolean notifyWindowUnresponsive(@NonNull IBinder inputToken, String reason) {
         preDumpIfLockTooSlow();
         final int pid;
         final boolean aboveSystem;
@@ -83,10 +111,8 @@
         synchronized (mService.mGlobalLock) {
             InputTarget target = mService.getInputTargetFromToken(inputToken);
             if (target == null) {
-                Slog.e(TAG_WM, "Unknown token, dropping notifyConnectionUnresponsive request");
-                return;
+                return false;
             }
-
             WindowState windowState = target.getWindowState();
             pid = target.getPid();
             // Blame the activity if the input token belongs to the window. If the target is
@@ -102,34 +128,63 @@
         } else {
             mService.mAmInternal.inputDispatchingTimedOut(pid, aboveSystem, reason);
         }
+        return true;
     }
 
-    void notifyWindowResponsive(IBinder inputToken) {
+    /**
+     * Notify a window owned by the provided pid was unresponsive.
+     */
+    private void notifyWindowUnresponsive(int pid, String reason) {
+        Slog.i(TAG_WM, "ANR in input window owned by pid=" + pid + ". Reason: " + reason);
+        dumpAnrStateLocked(null /* activity */, null /* windowState */, reason);
+
+        // We cannot determine the z-order of the window, so place the anr dialog as high
+        // as possible.
+        mService.mAmInternal.inputDispatchingTimedOut(pid, true /*aboveSystem*/, reason);
+    }
+
+    /**
+     * Notify a window was responsive after previously being unresponsive.
+     *
+     * @param token - the input token of the window
+     * @param pid - the pid of the window, if known
+     */
+    void notifyWindowResponsive(@NonNull IBinder token, @NonNull OptionalInt pid) {
+        if (notifyWindowResponsive(token)) {
+            return;
+        }
+        if (!pid.isPresent()) {
+            Slog.w(TAG_WM, "Failed to notify that window token=" + token + " was responsive.");
+            return;
+        }
+        notifyWindowResponsive(pid.getAsInt());
+    }
+
+    /**
+     * Notify a window identified by its input token was responsive after previously being
+     * unresponsive.
+     *
+     * @return true if the window was identified by the given input token and the request was
+     *         handled, false otherwise.
+     */
+    private boolean notifyWindowResponsive(@NonNull IBinder inputToken) {
         final int pid;
         synchronized (mService.mGlobalLock) {
             InputTarget target = mService.getInputTargetFromToken(inputToken);
             if (target == null) {
-                Slog.e(TAG_WM, "Unknown token, dropping notifyWindowConnectionResponsive request");
-                return;
+                return false;
             }
             pid = target.getPid();
         }
         mService.mAmInternal.inputDispatchingResumed(pid);
+        return true;
     }
 
-    void notifyGestureMonitorUnresponsive(int gestureMonitorPid, String reason) {
-        preDumpIfLockTooSlow();
-        synchronized (mService.mGlobalLock) {
-            Slog.i(TAG_WM, "ANR in gesture monitor owned by pid:" + gestureMonitorPid
-                    + ".  Reason: " + reason);
-            dumpAnrStateLocked(null /* activity */, null /* windowState */, reason);
-        }
-        mService.mAmInternal.inputDispatchingTimedOut(gestureMonitorPid, /* aboveSystem */ true,
-                reason);
-    }
-
-    void notifyGestureMonitorResponsive(int gestureMonitorPid) {
-        mService.mAmInternal.inputDispatchingResumed(gestureMonitorPid);
+    /**
+     * Notify a window owned by the provided pid was responsive after previously being unresponsive.
+     */
+    private void notifyWindowResponsive(int pid) {
+        mService.mAmInternal.inputDispatchingResumed(pid);
     }
 
     /**
@@ -228,12 +283,7 @@
         mService.mAtmService.saveANRState(reason);
     }
 
-    private boolean isWindowAboveSystem(WindowState windowState) {
-        if (windowState == null) {
-            // If the window state is not available we cannot easily determine its z order. Try to
-            // place the anr dialog as high as possible.
-            return true;
-        }
+    private boolean isWindowAboveSystem(@NonNull WindowState windowState) {
         int systemAlertLayer = mService.mPolicy.getWindowLayerFromTypeLw(
                 TYPE_APPLICATION_OVERLAY, windowState.mOwnerCanAddInternalSystemWindow);
         return windowState.mBaseLayer > systemAlertLayer;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4c5c705..83ff2f0 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5009,6 +5009,10 @@
             final boolean canImeTargetSetRelativeLayer = imeTarget.getSurfaceControl() != null
                     && imeTarget.mToken == imeControlTargetToken
                     && !imeTarget.inMultiWindowMode()
+                    // We don't need to set relative layer if the IME target in non-multi-window
+                    // mode is the activity main window since updateImeParent will ensure the IME
+                    // surface be attached on the fullscreen activity.
+                    && imeTarget.mAttrs.type != TYPE_BASE_APPLICATION
                     && imeTarget.mToken.getActivity(app -> app.isAnimating(TRANSITION | PARENTS,
                             ANIMATION_TYPE_ALL & ~ANIMATION_TYPE_RECENTS)) == null;
             if (canImeTargetSetRelativeLayer) {
@@ -5499,7 +5503,7 @@
      * display, which set unrestricted keep-clear areas.
      *
      * For context on restricted vs unrestricted keep-clear areas, see
-     * {@link android.Manifest.permission.USE_UNRESTRICTED_KEEP_CLEAR_AREAS}.
+     * {@link android.Manifest.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS}.
      */
     void getKeepClearAreas(List<Rect> outRestricted, List<Rect> outUnrestricted) {
         final Matrix tmpMatrix = new Matrix();
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 1f0fdcf..8d1425d 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -39,6 +39,7 @@
 import com.android.server.input.InputManagerService;
 
 import java.io.PrintWriter;
+import java.util.OptionalInt;
 
 final class InputManagerCallback implements InputManagerService.WindowManagerCallbacks {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "InputManagerCallback" : TAG_WM;
@@ -98,23 +99,14 @@
     }
 
     @Override
-    public void notifyGestureMonitorUnresponsive(int pid, @NonNull String reason) {
-        mService.mAnrController.notifyGestureMonitorUnresponsive(pid, reason);
+    public void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid,
+            @NonNull String reason) {
+        mService.mAnrController.notifyWindowUnresponsive(token, pid, reason);
     }
 
     @Override
-    public void notifyWindowUnresponsive(@NonNull IBinder token, String reason) {
-        mService.mAnrController.notifyWindowUnresponsive(token, reason);
-    }
-
-    @Override
-    public void notifyGestureMonitorResponsive(int pid) {
-        mService.mAnrController.notifyGestureMonitorResponsive(pid);
-    }
-
-    @Override
-    public void notifyWindowResponsive(@NonNull IBinder token) {
-        mService.mAnrController.notifyWindowResponsive(token);
+    public void notifyWindowResponsive(@NonNull IBinder token, @NonNull OptionalInt pid) {
+        mService.mAnrController.notifyWindowResponsive(token, pid);
     }
 
     /** Notifies that the input device configuration has changed. */
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index 8a2d116..160fc95 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -542,7 +542,7 @@
         if (mLockTaskModeTasks.isEmpty()) {
             return;
         }
-        task.performClearTaskLocked();
+        task.performClearTaskForReuse(false /* excludingTaskOverlay*/);
         mSupervisor.mRootWindowContainer.resumeFocusedTasksTopActivities();
     }
 
@@ -740,7 +740,7 @@
             ProtoLog.d(WM_DEBUG_LOCKTASK, "onLockTaskPackagesUpdated: removing %s"
                     + " mLockTaskAuth()=%s", lockedTask, lockedTask.lockTaskAuthToString());
             removeLockedTask(lockedTask);
-            lockedTask.performClearTaskLocked();
+            lockedTask.performClearTaskForReuse(false /* excludingTaskOverlay*/);
             taskChanged = true;
         }
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 8ab2ee0..b9cd657 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -18,15 +18,9 @@
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.KeyguardManager.ACTION_CONFIRM_DEVICE_CREDENTIAL_WITH_USER;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
@@ -2791,35 +2785,17 @@
         Task rootTask = null;
 
         // Next preference for root task goes to the taskDisplayArea candidate.
-        if (launchParams != null && launchParams.mPreferredTaskDisplayArea != null) {
+        if (launchParams != null && launchParams.mPreferredTaskDisplayArea != null
+                && canLaunchOnDisplay(r, launchParams.mPreferredTaskDisplayArea.getDisplayId())) {
             taskDisplayArea = launchParams.mPreferredTaskDisplayArea;
         }
-
-        if (taskDisplayArea == null && displayId != INVALID_DISPLAY) {
-            final DisplayContent displayContent = getDisplayContent(displayId);
-            if (displayContent != null) {
-                taskDisplayArea = displayContent.getDefaultTaskDisplayArea();
-            }
+        if (taskDisplayArea == null && displayId != INVALID_DISPLAY
+                && canLaunchOnDisplay(r, displayId)) {
+            taskDisplayArea = getDisplayContent(displayId).getDefaultTaskDisplayArea();
         }
-
         if (taskDisplayArea != null) {
-            final int tdaDisplayId = taskDisplayArea.getDisplayId();
-            if (canLaunchOnDisplay(r, tdaDisplayId)) {
-                if (r != null) {
-                    final Task result = getValidLaunchRootTaskInTaskDisplayArea(
-                            taskDisplayArea, r, candidateTask, options, launchParams);
-                    if (result != null) {
-                        return result;
-                    }
-                }
-                // Falling back to default task container
-                taskDisplayArea = taskDisplayArea.mDisplayContent.getDefaultTaskDisplayArea();
-                rootTask = taskDisplayArea.getOrCreateRootTask(r, options, candidateTask,
-                        sourceTask, launchParams, launchFlags, activityType, onTop);
-                if (rootTask != null) {
-                    return rootTask;
-                }
-            }
+            return taskDisplayArea.getOrCreateRootTask(r, options, candidateTask,
+                    sourceTask, launchParams, launchFlags, activityType, onTop);
         }
 
         // Give preference to the root task and display of the input task and activity if they
@@ -2869,103 +2845,6 @@
         return r.canBeLaunchedOnDisplay(displayId);
     }
 
-    /**
-     * Get a topmost root task on the display area, that is a valid launch root task for
-     * specified activity. If there is no such root task, new dynamic root task can be created.
-     *
-     * @param taskDisplayArea Target display area.
-     * @param r               Activity that should be launched there.
-     * @param candidateTask   The possible task the activity might be put in.
-     * @return Existing root task if there is a valid one, new dynamic root task if it is valid
-     * or null.
-     */
-    @VisibleForTesting
-    Task getValidLaunchRootTaskInTaskDisplayArea(@NonNull TaskDisplayArea taskDisplayArea,
-            @NonNull ActivityRecord r, @Nullable Task candidateTask,
-            @Nullable ActivityOptions options,
-            @Nullable LaunchParamsController.LaunchParams launchParams) {
-        if (!r.canBeLaunchedOnDisplay(taskDisplayArea.getDisplayId())) {
-            return null;
-        }
-
-        // If {@code r} is already in target display area and its task is the same as the candidate
-        // task, the intention should be getting a launch root task for the reusable activity, so we
-        // can use the existing root task.
-        if (candidateTask != null) {
-            final TaskDisplayArea attachedTaskDisplayArea = candidateTask.getDisplayArea();
-            if (attachedTaskDisplayArea == null || attachedTaskDisplayArea == taskDisplayArea) {
-                return candidateTask.getRootTask();
-            }
-            // Or the candidate task is already a root task that can be reused by reparenting
-            // it to the target display.
-            if (candidateTask.isRootTask()) {
-                final Task rootTask = candidateTask.getRootTask();
-                rootTask.reparent(taskDisplayArea, true /* onTop */);
-                return rootTask;
-            }
-        }
-
-        int windowingMode;
-        if (launchParams != null) {
-            // When launch params is not null, we always defer to its windowing mode. Sometimes
-            // it could be unspecified, which indicates it should inherit windowing mode from
-            // display.
-            windowingMode = launchParams.mWindowingMode;
-        } else {
-            windowingMode = options != null ? options.getLaunchWindowingMode()
-                    : r.getWindowingMode();
-        }
-        windowingMode = taskDisplayArea.validateWindowingMode(windowingMode, r, candidateTask);
-
-        // Return the topmost valid root task on the display.
-        final int targetWindowingMode = windowingMode;
-        final Task topmostValidRootTask = taskDisplayArea.getRootTask(rootTask ->
-                isValidLaunchRootTask(rootTask, r, targetWindowingMode));
-        if (topmostValidRootTask != null) {
-            return topmostValidRootTask;
-        }
-
-        // If there is no valid root task on the secondary display area - check if new dynamic root
-        // task will do.
-        if (taskDisplayArea != getDisplayContent(taskDisplayArea.getDisplayId())
-                .getDefaultTaskDisplayArea()) {
-            final int activityType =
-                    options != null && options.getLaunchActivityType() != ACTIVITY_TYPE_UNDEFINED
-                            ? options.getLaunchActivityType() : r.getActivityType();
-            return taskDisplayArea.createRootTask(
-                    windowingMode, activityType, true /*onTop*/, options);
-        }
-
-        return null;
-    }
-
-    // TODO: Can probably be consolidated into getLaunchRootTask()...
-    private boolean isValidLaunchRootTask(Task task, ActivityRecord r, int windowingMode) {
-        switch (task.getActivityType()) {
-            case ACTIVITY_TYPE_HOME:
-                return r.isActivityTypeHome();
-            case ACTIVITY_TYPE_RECENTS:
-                return r.isActivityTypeRecents();
-            case ACTIVITY_TYPE_ASSISTANT:
-                return r.isActivityTypeAssistant();
-            case ACTIVITY_TYPE_DREAM:
-                return r.isActivityTypeDream();
-        }
-        if (task.mCreatedByOrganizer) {
-            // Don't launch directly into task created by organizer...but why can't we?
-            return false;
-        }
-        // There is a 1-to-1 relationship between root task and task when not in
-        // primary split-windowing mode.
-        if (task.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                && r.supportsSplitScreenWindowingModeInDisplayArea(task.getDisplayArea())
-                && (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                || windowingMode == WINDOWING_MODE_UNDEFINED)) {
-            return true;
-        }
-        return false;
-    }
-
     int resolveActivityType(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
             @Nullable Task task) {
         // Preference is given to the activity type for the activity then the task since the type
@@ -3434,11 +3313,11 @@
      * or all visible root tasks if {@param dumpVisibleRootTasksOnly} is true.
      */
     ArrayList<ActivityRecord> getDumpActivities(String name, boolean dumpVisibleRootTasksOnly,
-            boolean dumpFocusedRootTaskOnly) {
+            boolean dumpFocusedRootTaskOnly, @UserIdInt int userId) {
         if (dumpFocusedRootTaskOnly) {
             final Task topFocusedRootTask = getTopDisplayFocusedRootTask();
             if (topFocusedRootTask != null) {
-                return topFocusedRootTask.getDumpActivitiesLocked(name);
+                return topFocusedRootTask.getDumpActivitiesLocked(name, userId);
             } else {
                 return new ArrayList<>();
             }
@@ -3446,7 +3325,7 @@
             final ArrayList<ActivityRecord> activities = new ArrayList<>();
             forAllRootTasks(rootTask -> {
                 if (!dumpVisibleRootTasksOnly || rootTask.shouldBeVisible(null)) {
-                    activities.addAll(rootTask.getDumpActivitiesLocked(name));
+                    activities.addAll(rootTask.getDumpActivitiesLocked(name, userId));
                 }
             });
             return activities;
diff --git a/services/core/java/com/android/server/wm/ShellRoot.java b/services/core/java/com/android/server/wm/ShellRoot.java
index 2eab3ba..f9d7b53 100644
--- a/services/core/java/com/android/server/wm/ShellRoot.java
+++ b/services/core/java/com/android/server/wm/ShellRoot.java
@@ -25,15 +25,14 @@
 import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.Point;
-import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
 import android.view.DisplayInfo;
 import android.view.IWindow;
 import android.view.SurfaceControl;
-import android.view.WindowInfo;
 import android.view.WindowManager;
 import android.view.animation.Animation;
 
@@ -136,46 +135,12 @@
                 ANIMATION_TYPE_WINDOW_ANIMATION);
     }
 
-    WindowInfo getWindowInfo() {
-        if (mShellRootLayer != SHELL_ROOT_LAYER_DIVIDER
-                && mShellRootLayer != SHELL_ROOT_LAYER_PIP) {
-            return null;
+    @Nullable
+    IBinder getAccessibilityWindowToken() {
+        if (mAccessibilityWindow != null) {
+            return mAccessibilityWindow.asBinder();
         }
-        if (mShellRootLayer == SHELL_ROOT_LAYER_DIVIDER) {
-            return null;
-        }
-        if (mShellRootLayer == SHELL_ROOT_LAYER_PIP
-                && mDisplayContent.getDefaultTaskDisplayArea().getRootPinnedTask() == null) {
-            return null;
-        }
-        if (mAccessibilityWindow == null) {
-            return null;
-        }
-        WindowInfo windowInfo = WindowInfo.obtain();
-        windowInfo.displayId = mToken.getDisplayArea().getDisplayContent().mDisplayId;
-        windowInfo.type = mToken.windowType;
-        windowInfo.layer = mToken.getWindowLayerFromType();
-        windowInfo.token = mAccessibilityWindow.asBinder();
-        windowInfo.focused = false;
-        windowInfo.hasFlagWatchOutsideTouch = false;
-        final Rect regionRect = new Rect();
-
-
-        // DividerView
-        if (mShellRootLayer == SHELL_ROOT_LAYER_DIVIDER) {
-            windowInfo.inPictureInPicture = false;
-            mDisplayContent.getDockedDividerController().getTouchRegion(regionRect);
-            windowInfo.regionInScreen.set(regionRect);
-            windowInfo.title = "Splitscreen Divider";
-        }
-        // PipMenuView
-        if (mShellRootLayer == SHELL_ROOT_LAYER_PIP) {
-            windowInfo.inPictureInPicture = true;
-            mDisplayContent.getDefaultTaskDisplayArea().getRootPinnedTask().getBounds(regionRect);
-            windowInfo.regionInScreen.set(regionRect);
-            windowInfo.title = "Picture-in-Picture menu";
-        }
-        return windowInfo;
+        return null;
     }
 
     void setAccessibilityWindow(IWindow window) {
@@ -196,9 +161,5 @@
                 mAccessibilityWindow = null;
             }
         }
-        if (mDisplayContent.mWmService.mAccessibilityController.hasCallbacks()) {
-            mDisplayContent.mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(
-                    mDisplayContent.getDisplayId());
-        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ffa1a60..cc03c60 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -137,6 +137,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData;
@@ -1583,19 +1584,23 @@
     }
 
     /** Completely remove all activities associated with an existing task. */
-    void performClearTask(String reason) {
+    void removeActivities(String reason, boolean excludingTaskOverlay) {
         clearPinnedTaskIfNeed();
         // Broken down into to cases to avoid object create due to capturing mStack.
         if (getRootTask() == null) {
             forAllActivities((r) -> {
-                if (r.finishing) return;
+                if (r.finishing || (excludingTaskOverlay && r.isTaskOverlay())) {
+                    return;
+                }
                 // Task was restored from persistent storage.
                 r.takeFromHistory();
                 removeChild(r, reason);
             });
         } else {
             forAllActivities((r) -> {
-                if (r.finishing) return;
+                if (r.finishing || (excludingTaskOverlay && r.isTaskOverlay())) {
+                    return;
+                }
                 // Prevent the transition from being executed too early if the top activity is
                 // resumed but the mVisibleRequested of any other activity is true, the transition
                 // should wait until next activity resumed.
@@ -1612,26 +1617,24 @@
     /**
      * Completely remove all activities associated with an existing task.
      */
-    void performClearTaskLocked() {
+    void performClearTaskForReuse(boolean excludingTaskOverlay) {
         mReuseTask = true;
         mTaskSupervisor.beginDeferResume();
         try {
-            performClearTask("clear-task-all");
+            removeActivities("clear-task-all", excludingTaskOverlay);
         } finally {
             mTaskSupervisor.endDeferResume();
             mReuseTask = false;
         }
     }
 
-    ActivityRecord performClearTaskForReuseLocked(ActivityRecord newR, int launchFlags) {
-        mReuseTask = true;
+    ActivityRecord performClearTop(ActivityRecord newR, int launchFlags) {
         mTaskSupervisor.beginDeferResume();
         final ActivityRecord result;
         try {
-            result = performClearTaskLocked(newR, launchFlags);
+            result = clearTopActivities(newR, launchFlags);
         } finally {
             mTaskSupervisor.endDeferResume();
-            mReuseTask = false;
         }
         return result;
     }
@@ -1647,7 +1650,7 @@
      * @return Returns the old activity that should be continued to be used,
      * or {@code null} if none was found.
      */
-    private ActivityRecord performClearTaskLocked(ActivityRecord newR, int launchFlags) {
+    private ActivityRecord clearTopActivities(ActivityRecord newR, int launchFlags) {
         final ActivityRecord r = findActivityInHistory(newR.mActivityComponent);
         if (r == null) return null;
 
@@ -1674,7 +1677,7 @@
         // Stop operation once we reach the boundary activity.
         if (r == boundaryActivity) return true;
 
-        if (!r.finishing) {
+        if (!r.finishing && !r.isTaskOverlay()) {
             final ActivityOptions opts = r.getOptions();
             if (opts != null) {
                 r.clearOptionsAnimation();
@@ -5149,10 +5152,13 @@
             // Ensure that we do not trigger entering PiP an activity on the root pinned task
             return false;
         }
+        final boolean isTransient = opts != null && opts.getTransientLaunch();
         final Task targetRootTask = toFrontTask != null
                 ? toFrontTask.getRootTask() : toFrontActivity.getRootTask();
-        if (targetRootTask != null && targetRootTask.isActivityTypeAssistant()) {
-            // Ensure the task/activity being brought forward is not the assistant
+        if (targetRootTask != null && (targetRootTask.isActivityTypeAssistant() || isTransient)) {
+            // Ensure the task/activity being brought forward is not the assistant and is not
+            // transient. In the case of transient-launch, we want to wait until the end of the
+            // transition and only allow switch if the transient launch was committed.
             return false;
         }
         return true;
@@ -5688,7 +5694,7 @@
         }
     }
 
-    ArrayList<ActivityRecord> getDumpActivitiesLocked(String name) {
+    ArrayList<ActivityRecord> getDumpActivitiesLocked(String name, @UserIdInt int userId) {
         ArrayList<ActivityRecord> activities = new ArrayList<>();
 
         if ("all".equals(name)) {
@@ -5708,7 +5714,13 @@
                 }
             });
         }
-
+        if (userId != UserHandle.USER_ALL) {
+            for (int i = activities.size() - 1; i >= 0; --i) {
+                if (activities.get(i).mUserId != userId) {
+                    activities.remove(i);
+                }
+            }
+        }
         return activities;
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index f0cca18..2f50b14 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -941,36 +941,32 @@
     Task getOrCreateRootTask(int windowingMode, int activityType, boolean onTop,
             @Nullable Task candidateTask, @Nullable Task sourceTask,
             @Nullable ActivityOptions options, int launchFlags) {
+        final int resolvedWindowingMode =
+                windowingMode == WINDOWING_MODE_UNDEFINED ? getWindowingMode() : windowingMode;
         // Need to pass in a determined windowing mode to see if a new root task should be created,
         // so use its parent's windowing mode if it is undefined.
-        if (!alwaysCreateRootTask(
-                windowingMode != WINDOWING_MODE_UNDEFINED ? windowingMode : getWindowingMode(),
-                activityType)) {
-            Task rootTask = getRootTask(windowingMode, activityType);
+        if (!alwaysCreateRootTask(resolvedWindowingMode, activityType)) {
+            Task rootTask = getRootTask(resolvedWindowingMode, activityType);
             if (rootTask != null) {
                 return rootTask;
             }
         } else if (candidateTask != null) {
             final int position = onTop ? POSITION_TOP : POSITION_BOTTOM;
-            final Task launchRootTask = getLaunchRootTask(windowingMode, activityType, options,
-                    sourceTask, launchFlags);
+            final Task launchRootTask = getLaunchRootTask(resolvedWindowingMode, activityType,
+                    options, sourceTask, launchFlags);
             if (launchRootTask != null) {
                 if (candidateTask.getParent() == null) {
                     launchRootTask.addChild(candidateTask, position);
                 } else if (candidateTask.getParent() != launchRootTask) {
                     candidateTask.reparent(launchRootTask, position);
                 }
-            } else if (candidateTask.getDisplayArea() != this || !candidateTask.isRootTask()) {
+            } else if (candidateTask.getDisplayArea() != this) {
                 if (candidateTask.getParent() == null) {
                     addChild(candidateTask, position);
                 } else {
                     candidateTask.reparent(this, onTop);
                 }
             }
-            // Update windowing mode if necessary, e.g. moving a pinned task to fullscreen.
-            if (candidateTask.getWindowingMode() != windowingMode) {
-                candidateTask.setWindowingMode(windowingMode);
-            }
             return candidateTask.getRootTask();
         }
         return new Task.Builder(mAtmService)
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index b8ceb4a..9bb0271 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -157,12 +157,26 @@
         // display.
         if (launchMode == WINDOWING_MODE_UNDEFINED
                 && canInheritWindowingModeFromSource(display, source)) {
-            launchMode = source.getWindowingMode();
+            // The source's windowing mode may be different from its task, e.g. activity is set
+            // to fullscreen and its task is pinned windowing mode when the activity is entering
+            // pip.
+            launchMode = source.getTask().getWindowingMode();
             if (DEBUG) {
                 appendLog("inherit-from-source="
                         + WindowConfiguration.windowingModeToString(launchMode));
             }
         }
+        // If the launch windowing mode is still undefined, inherit from the target task if the
+        // task is already on the right display area (otherwise, the task may be on a different
+        // display area that has incompatible windowing mode).
+        if (launchMode == WINDOWING_MODE_UNDEFINED
+                && task != null && task.getTaskDisplayArea() == suggestedDisplayArea) {
+            launchMode = task.getWindowingMode();
+            if (DEBUG) {
+                appendLog("inherit-from-task="
+                        + WindowConfiguration.windowingModeToString(launchMode));
+            }
+        }
         // hasInitialBounds is set if either activity options or layout has specified bounds. If
         // that's set we'll skip some adjustments later to avoid overriding the initial bounds.
         boolean hasInitialBounds = false;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index a9add59..6a23eb5 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -310,7 +310,7 @@
         }
         final ActivityRecord activity = result.first;
         final WindowState mainWindow = result.second;
-        final Rect contentInsets = getSystemBarInsets(task.getBounds(),
+        final Rect contentInsets = getSystemBarInsets(mainWindow.getFrame(),
                 mainWindow.getInsetsStateWithVisibilityOverride());
         final Rect letterboxInsets = activity.getLetterboxInsets();
         InsetUtils.addInsets(contentInsets, letterboxInsets);
@@ -575,7 +575,7 @@
         final LayoutParams attrs = mainWindow.getAttrs();
         final Rect taskBounds = task.getBounds();
         final InsetsState insetsState = mainWindow.getInsetsStateWithVisibilityOverride();
-        final Rect systemBarInsets = getSystemBarInsets(taskBounds, insetsState);
+        final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrame(), insetsState);
         final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
                 attrs.privateFlags, attrs.insetsFlags.appearance, task.getTaskDescription(),
                 mHighResTaskSnapshotScale, insetsState);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 220381d..0dfee60 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -460,9 +460,19 @@
                 // activity in a bad state.
                 if (!visibleAtTransitionEnd && !ar.isVisibleRequested()) {
                     boolean commitVisibility = true;
-                    if (ar.getDeferHidingClient() && ar.getTask() != null) {
+                    if (ar.isVisible() && ar.getTask() != null) {
                         if (ar.pictureInPictureArgs != null
                                 && ar.pictureInPictureArgs.isAutoEnterEnabled()) {
+                            if (mTransientLaunches != null) {
+                                for (int j = 0; j < mTransientLaunches.size(); ++j) {
+                                    if (mTransientLaunches.valueAt(j).isVisibleRequested()) {
+                                        // force enable pip-on-task-switch now that we've committed
+                                        // to actually launching to the transient activity.
+                                        ar.supportsEnterPipOnTaskSwitch = true;
+                                        break;
+                                    }
+                                }
+                            }
                             mController.mAtm.enterPictureInPictureMode(ar, ar.pictureInPictureArgs);
                             // Avoid commit visibility to false here, or else we will get a sudden
                             // "flash" / surface going invisible for a split second.
@@ -1257,7 +1267,11 @@
                 change.setAllowEnterPip(topMostActivity != null
                         && topMostActivity.checkEnterPictureInPictureAppOpsState());
                 final ActivityRecord topRunningActivity = task.topRunningActivity();
-                if (topRunningActivity != null && task.mDisplayContent != null) {
+                if (topRunningActivity != null && task.mDisplayContent != null
+                        // Display won't be rotated for multi window Task, so the fixed rotation
+                        // won't be applied. This can happen when the windowing mode is changed
+                        // before the previous fixed rotation is applied.
+                        && !task.inMultiWindowMode()) {
                     // If Activity is in fixed rotation, its will be applied with the next rotation,
                     // when the Task is still in the previous rotation.
                     final int taskRotation = task.getWindowConfiguration().getDisplayRotation();
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index c267cba..c13ae95 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -30,6 +30,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.IApplicationThread;
+import android.app.WindowConfiguration;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.os.RemoteException;
@@ -296,6 +297,17 @@
         return ci.mVisible;
     }
 
+    @WindowConfiguration.WindowingMode
+    int getWindowingModeAtStart(@NonNull WindowContainer wc) {
+        if (mCollectingTransition == null) return wc.getWindowingMode();
+        final Transition.ChangeInfo ci = mCollectingTransition.mChanges.get(wc);
+        if (ci == null) {
+            // not part of transition, so use current state.
+            return wc.getWindowingMode();
+        }
+        return ci.mWindowingMode;
+    }
+
     @WindowManager.TransitionType
     int getCollectingTransitionType() {
         return mCollectingTransition != null ? mCollectingTransition.mType : TRANSIT_NONE;
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 24493e2..14737d4 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -196,8 +196,11 @@
                         "Win " + w + ": token animating, looking behind.");
             }
             mFindResults.setIsWallpaperTargetForLetterbox(w.hasWallpaperForLetterboxBackground());
-            // Found a target! End search.
-            return true;
+            // While the keyguard is going away, both notification shade and a normal activity such
+            // as a launcher can satisfy criteria for a wallpaper target. In this case, we should
+            // chose the normal activity, otherwise wallpaper becomes invisible when a new animation
+            // starts before the keyguard going away animation finishes.
+            return w.mActivityRecord != null;
         }
         return false;
     };
diff --git a/services/core/java/com/android/server/wm/WindowOrientationListener.java b/services/core/java/com/android/server/wm/WindowOrientationListener.java
index a967ea8..de87ab9 100644
--- a/services/core/java/com/android/server/wm/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/wm/WindowOrientationListener.java
@@ -1167,6 +1167,10 @@
                 if (mRotationResolverService == null) {
                     mRotationResolverService = LocalServices.getService(
                             RotationResolverInternal.class);
+                    if (mRotationResolverService == null) {
+                        finalizeRotation(reportedRotation);
+                        return;
+                    }
                 }
 
                 String packageName = null;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 87ef09e..26acf43 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -4992,9 +4992,6 @@
         if (isAnimating()) {
             return;
         }
-        if (mWmService.mAccessibilityController.hasCallbacks()) {
-            mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(getDisplayId());
-        }
 
         if (!isSelfOrAncestorWindowAnimatingExit()) {
             return;
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index ccaa03a..a2e8813 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 
@@ -645,8 +646,10 @@
         final ActivityRecord r = asActivityRecord();
         if (r != null) {
             final Task rootTask = r.getRootTask();
-            // Don't transform the activity in PiP because the PiP task organizer will handle it.
-            if (rootTask != null && rootTask.inPinnedWindowingMode()) {
+            // Don't transform the activity exiting PiP because the PiP task organizer will handle
+            // it.
+            if (rootTask != null && mTransitionController.getWindowingModeAtStart(rootTask)
+                    == WINDOWING_MODE_PINNED) {
                 return;
             }
         }
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 3c122b0..31b5579 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -96,8 +96,6 @@
     jmethodID notifyNoFocusedWindowAnr;
     jmethodID notifyWindowUnresponsive;
     jmethodID notifyWindowResponsive;
-    jmethodID notifyMonitorUnresponsive;
-    jmethodID notifyMonitorResponsive;
     jmethodID notifyFocusChanged;
     jmethodID notifySensorEvent;
     jmethodID notifySensorAccuracy;
@@ -308,10 +306,9 @@
     void notifyConfigurationChanged(nsecs_t when) override;
     // ANR-related callbacks -- start
     void notifyNoFocusedWindowAnr(const std::shared_ptr<InputApplicationHandle>& handle) override;
-    void notifyWindowUnresponsive(const sp<IBinder>& token, const std::string& reason) override;
-    void notifyWindowResponsive(const sp<IBinder>& token) override;
-    void notifyMonitorUnresponsive(int32_t pid, const std::string& reason) override;
-    void notifyMonitorResponsive(int32_t pid) override;
+    void notifyWindowUnresponsive(const sp<IBinder>& token, std::optional<int32_t> pid,
+                                  const std::string& reason) override;
+    void notifyWindowResponsive(const sp<IBinder>& token, std::optional<int32_t> pid) override;
     // ANR-related callbacks -- end
     void notifyInputChannelBroken(const sp<IBinder>& token) override;
     void notifyFocusChanged(const sp<IBinder>& oldToken, const sp<IBinder>& newToken) override;
@@ -838,6 +835,7 @@
 }
 
 void NativeInputManager::notifyWindowUnresponsive(const sp<IBinder>& token,
+                                                  std::optional<int32_t> pid,
                                                   const std::string& reason) {
 #if DEBUG_INPUT_DISPATCHER_POLICY
     ALOGD("notifyWindowUnresponsive");
@@ -851,11 +849,12 @@
     ScopedLocalRef<jstring> reasonObj(env, env->NewStringUTF(reason.c_str()));
 
     env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyWindowUnresponsive, tokenObj,
-                        reasonObj.get());
+                        pid.value_or(0), pid.has_value(), reasonObj.get());
     checkAndClearExceptionFromCallback(env, "notifyWindowUnresponsive");
 }
 
-void NativeInputManager::notifyWindowResponsive(const sp<IBinder>& token) {
+void NativeInputManager::notifyWindowResponsive(const sp<IBinder>& token,
+                                                std::optional<int32_t> pid) {
 #if DEBUG_INPUT_DISPATCHER_POLICY
     ALOGD("notifyWindowResponsive");
 #endif
@@ -866,39 +865,11 @@
 
     jobject tokenObj = javaObjectForIBinder(env, token);
 
-    env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyWindowResponsive, tokenObj);
+    env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyWindowResponsive, tokenObj,
+                        pid.value_or(0), pid.has_value());
     checkAndClearExceptionFromCallback(env, "notifyWindowResponsive");
 }
 
-void NativeInputManager::notifyMonitorUnresponsive(int32_t pid, const std::string& reason) {
-#if DEBUG_INPUT_DISPATCHER_POLICY
-    ALOGD("notifyMonitorUnresponsive");
-#endif
-    ATRACE_CALL();
-
-    JNIEnv* env = jniEnv();
-    ScopedLocalFrame localFrame(env);
-
-    ScopedLocalRef<jstring> reasonObj(env, env->NewStringUTF(reason.c_str()));
-
-    env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyMonitorUnresponsive, pid,
-                        reasonObj.get());
-    checkAndClearExceptionFromCallback(env, "notifyMonitorUnresponsive");
-}
-
-void NativeInputManager::notifyMonitorResponsive(int32_t pid) {
-#if DEBUG_INPUT_DISPATCHER_POLICY
-    ALOGD("notifyMonitorResponsive");
-#endif
-    ATRACE_CALL();
-
-    JNIEnv* env = jniEnv();
-    ScopedLocalFrame localFrame(env);
-
-    env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyMonitorResponsive, pid);
-    checkAndClearExceptionFromCallback(env, "notifyMonitorResponsive");
-}
-
 void NativeInputManager::notifyInputChannelBroken(const sp<IBinder>& token) {
 #if DEBUG_INPUT_DISPATCHER_POLICY
     ALOGD("notifyInputChannelBroken");
@@ -2506,16 +2477,10 @@
                   "(Landroid/view/InputApplicationHandle;)V");
 
     GET_METHOD_ID(gServiceClassInfo.notifyWindowUnresponsive, clazz, "notifyWindowUnresponsive",
-                  "(Landroid/os/IBinder;Ljava/lang/String;)V");
-
-    GET_METHOD_ID(gServiceClassInfo.notifyMonitorUnresponsive, clazz, "notifyMonitorUnresponsive",
-                  "(ILjava/lang/String;)V");
+                  "(Landroid/os/IBinder;IZLjava/lang/String;)V");
 
     GET_METHOD_ID(gServiceClassInfo.notifyWindowResponsive, clazz, "notifyWindowResponsive",
-                  "(Landroid/os/IBinder;)V");
-
-    GET_METHOD_ID(gServiceClassInfo.notifyMonitorResponsive, clazz, "notifyMonitorResponsive",
-                  "(I)V");
+                  "(Landroid/os/IBinder;IZ)V");
 
     GET_METHOD_ID(gServiceClassInfo.filterInputEvent, clazz,
             "filterInputEvent", "(Landroid/view/InputEvent;I)Z");
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index a9c6b8d..11714dc 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -50,6 +50,7 @@
 #include "android_runtime/Log.h"
 #include "gnss/AGnss.h"
 #include "gnss/AGnssRil.h"
+#include "gnss/Gnss.h"
 #include "gnss/GnssAntennaInfo.h"
 #include "gnss/GnssAntennaInfoCallback.h"
 #include "gnss/GnssBatching.h"
@@ -68,18 +69,7 @@
 
 static jclass class_gnssPowerStats;
 
-static jmethodID method_reportLocation;
-static jmethodID method_reportStatus;
-static jmethodID method_reportSvStatus;
-static jmethodID method_reportNmea;
-static jmethodID method_setTopHalCapabilities;
-static jmethodID method_setGnssYearOfHardware;
-static jmethodID method_setGnssHardwareModelName;
-static jmethodID method_psdsDownloadRequest;
 static jmethodID method_reportNiNotification;
-static jmethodID method_requestLocation;
-static jmethodID method_requestUtcTime;
-static jmethodID method_reportGnssServiceDied;
 static jmethodID method_reportGnssPowerStats;
 static jmethodID method_reportNfwNotification;
 static jmethodID method_isInEmergencySession;
@@ -92,7 +82,6 @@
 using android::String16;
 using android::wp;
 using android::binder::Status;
-using android::gnss::GnssConfigurationInterface;
 
 using android::hardware::Return;
 using android::hardware::Void;
@@ -128,12 +117,8 @@
 using android::hardware::gnss::GnssPowerStats;
 using android::hardware::gnss::IGnssPowerIndication;
 using android::hardware::gnss::IGnssPowerIndicationCallback;
-using android::hardware::gnss::PsdsType;
 
-using IAGnssAidl = android::hardware::gnss::IAGnss;
-using IAGnssRilAidl = android::hardware::gnss::IAGnssRil;
 using IGnssAidl = android::hardware::gnss::IGnss;
-using IGnssCallbackAidl = android::hardware::gnss::IGnssCallback;
 using IGnssBatchingAidl = android::hardware::gnss::IGnssBatching;
 using IGnssDebugAidl = android::hardware::gnss::IGnssDebug;
 using IGnssPsdsAidl = android::hardware::gnss::IGnssPsds;
@@ -142,575 +127,30 @@
 using GnssLocationAidl = android::hardware::gnss::GnssLocation;
 using IGnssAntennaInfoAidl = android::hardware::gnss::IGnssAntennaInfo;
 
-struct GnssDeathRecipient : virtual public hidl_death_recipient
-{
-    // hidl_death_recipient interface
-    virtual void serviceDied(uint64_t cookie, const wp<IBase>& who) override {
-        ALOGE("IGNSS hidl service failed, trying to recover...");
-
-        JNIEnv* env = android::AndroidRuntime::getJNIEnv();
-        env->CallVoidMethod(android::mCallbacksObj, method_reportGnssServiceDied);
-    }
-};
-
-// Must match the value from GnssMeasurement.java
-static const uint32_t SVID_FLAGS_HAS_BASEBAND_CN0 = (1<<4);
-
-sp<GnssDeathRecipient> gnssHalDeathRecipient = nullptr;
-sp<IGnss_V1_0> gnssHal = nullptr;
-sp<IGnss_V1_1> gnssHal_V1_1 = nullptr;
-sp<IGnss_V2_0> gnssHal_V2_0 = nullptr;
-sp<IGnss_V2_1> gnssHal_V2_1 = nullptr;
-sp<IGnssAidl> gnssHalAidl = nullptr;
-sp<IGnssBatchingAidl> gnssBatchingAidlIface = nullptr;
-sp<IGnssPsdsAidl> gnssPsdsAidlIface = nullptr;
-sp<IGnssXtra> gnssXtraIface = nullptr;
 sp<IGnssNi> gnssNiIface = nullptr;
 sp<IGnssPowerIndication> gnssPowerIndicationIface = nullptr;
 
-std::unique_ptr<GnssConfigurationInterface> gnssConfigurationIface = nullptr;
+std::unique_ptr<android::gnss::GnssHal> gnssHal = nullptr;
+std::unique_ptr<android::gnss::AGnssInterface> agnssIface = nullptr;
+std::unique_ptr<android::gnss::AGnssRilInterface> agnssRilIface = nullptr;
+std::unique_ptr<android::gnss::GnssAntennaInfoInterface> gnssAntennaInfoIface = nullptr;
+std::unique_ptr<android::gnss::GnssConfigurationInterface> gnssConfigurationIface = nullptr;
 std::unique_ptr<android::gnss::GnssMeasurementInterface> gnssMeasurementIface = nullptr;
 std::unique_ptr<android::gnss::GnssNavigationMessageInterface> gnssNavigationMessageIface = nullptr;
 std::unique_ptr<android::gnss::GnssBatchingInterface> gnssBatchingIface = nullptr;
-std::unique_ptr<android::gnss::GnssGeofenceInterface> gnssGeofencingIface = nullptr;
-std::unique_ptr<android::gnss::AGnssInterface> agnssIface = nullptr;
 std::unique_ptr<android::gnss::GnssDebugInterface> gnssDebugIface = nullptr;
-std::unique_ptr<android::gnss::AGnssRilInterface> agnssRilIface = nullptr;
+std::unique_ptr<android::gnss::GnssGeofenceInterface> gnssGeofencingIface = nullptr;
+std::unique_ptr<android::gnss::GnssPsdsInterface> gnssPsdsIface = nullptr;
 std::unique_ptr<android::gnss::GnssVisibilityControlInterface> gnssVisibilityControlIface = nullptr;
-std::unique_ptr<android::gnss::GnssAntennaInfoInterface> gnssAntennaInfoIface = nullptr;
 std::unique_ptr<android::gnss::MeasurementCorrectionsInterface> gnssMeasurementCorrectionsIface =
         nullptr;
 
-#define WAKE_LOCK_NAME  "GPS"
-
 namespace android {
 
 namespace {
 
-// Returns true if location has lat/long information.
-bool hasLatLong(const GnssLocationAidl& location) {
-    return (location.gnssLocationFlags & GnssLocationAidl::HAS_LAT_LONG) != 0;
-}
-
-// Returns true if location has lat/long information.
-bool hasLatLong(const GnssLocation_V1_0& location) {
-    return (static_cast<uint32_t>(location.gnssLocationFlags) &
-            GnssLocationFlags::HAS_LAT_LONG) != 0;
-}
-
-// Returns true if location has lat/long information.
-bool hasLatLong(const GnssLocation_V2_0& location) {
-    return hasLatLong(location.v1_0);
-}
-
-bool isSvStatusRegistered = false;
-bool isNmeaRegistered = false;
-
 }  // namespace
 
-static inline jboolean boolToJbool(bool value) {
-    return value ? JNI_TRUE : JNI_FALSE;
-}
-
-static GnssLocationAidl createGnssLocation(jint gnssLocationFlags, jdouble latitudeDegrees,
-                                           jdouble longitudeDegrees, jdouble altitudeMeters,
-                                           jfloat speedMetersPerSec, jfloat bearingDegrees,
-                                           jfloat horizontalAccuracyMeters,
-                                           jfloat verticalAccuracyMeters,
-                                           jfloat speedAccuracyMetersPerSecond,
-                                           jfloat bearingAccuracyDegrees, jlong timestamp,
-                                           jint elapsedRealtimeFlags, jlong elapsedRealtimeNanos,
-                                           jdouble elapsedRealtimeUncertaintyNanos) {
-    GnssLocationAidl location;
-    location.gnssLocationFlags = static_cast<int>(gnssLocationFlags);
-    location.latitudeDegrees = static_cast<double>(latitudeDegrees);
-    location.longitudeDegrees = static_cast<double>(longitudeDegrees);
-    location.altitudeMeters = static_cast<double>(altitudeMeters);
-    location.speedMetersPerSec = static_cast<double>(speedMetersPerSec);
-    location.bearingDegrees = static_cast<double>(bearingDegrees);
-    location.horizontalAccuracyMeters = static_cast<double>(horizontalAccuracyMeters);
-    location.verticalAccuracyMeters = static_cast<double>(verticalAccuracyMeters);
-    location.speedAccuracyMetersPerSecond = static_cast<double>(speedAccuracyMetersPerSecond);
-    location.bearingAccuracyDegrees = static_cast<double>(bearingAccuracyDegrees);
-    location.timestampMillis = static_cast<uint64_t>(timestamp);
-
-    location.elapsedRealtime.flags = static_cast<int>(elapsedRealtimeFlags);
-    location.elapsedRealtime.timestampNs = static_cast<uint64_t>(elapsedRealtimeNanos);
-    location.elapsedRealtime.timeUncertaintyNs =
-            static_cast<double>(elapsedRealtimeUncertaintyNanos);
-
-    return location;
-}
-
-static GnssLocation_V1_0 createGnssLocation_V1_0(
-        jint gnssLocationFlags, jdouble latitudeDegrees, jdouble longitudeDegrees,
-        jdouble altitudeMeters, jfloat speedMetersPerSec, jfloat bearingDegrees,
-        jfloat horizontalAccuracyMeters, jfloat verticalAccuracyMeters,
-        jfloat speedAccuracyMetersPerSecond, jfloat bearingAccuracyDegrees,
-        jlong timestamp) {
-    GnssLocation_V1_0 location;
-    location.gnssLocationFlags = static_cast<uint16_t>(gnssLocationFlags);
-    location.latitudeDegrees = static_cast<double>(latitudeDegrees);
-    location.longitudeDegrees = static_cast<double>(longitudeDegrees);
-    location.altitudeMeters = static_cast<double>(altitudeMeters);
-    location.speedMetersPerSec = static_cast<float>(speedMetersPerSec);
-    location.bearingDegrees = static_cast<float>(bearingDegrees);
-    location.horizontalAccuracyMeters = static_cast<float>(horizontalAccuracyMeters);
-    location.verticalAccuracyMeters = static_cast<float>(verticalAccuracyMeters);
-    location.speedAccuracyMetersPerSecond = static_cast<float>(speedAccuracyMetersPerSecond);
-    location.bearingAccuracyDegrees = static_cast<float>(bearingAccuracyDegrees);
-    location.timestamp = static_cast<uint64_t>(timestamp);
-
-    return location;
-}
-
-static GnssLocation_V2_0 createGnssLocation_V2_0(
-        jint gnssLocationFlags, jdouble latitudeDegrees, jdouble longitudeDegrees,
-        jdouble altitudeMeters, jfloat speedMetersPerSec, jfloat bearingDegrees,
-        jfloat horizontalAccuracyMeters, jfloat verticalAccuracyMeters,
-        jfloat speedAccuracyMetersPerSecond, jfloat bearingAccuracyDegrees,
-        jlong timestamp, jint elapsedRealtimeFlags, jlong elapsedRealtimeNanos,
-        jdouble elapsedRealtimeUncertaintyNanos) {
-    GnssLocation_V2_0 location;
-    location.v1_0 = createGnssLocation_V1_0(
-            gnssLocationFlags, latitudeDegrees, longitudeDegrees, altitudeMeters,
-            speedMetersPerSec, bearingDegrees, horizontalAccuracyMeters,
-            verticalAccuracyMeters, speedAccuracyMetersPerSecond,
-            bearingAccuracyDegrees, timestamp);
-
-    location.elapsedRealtime.flags = static_cast<uint16_t>(elapsedRealtimeFlags);
-    location.elapsedRealtime.timestampNs = static_cast<uint64_t>(elapsedRealtimeNanos);
-    location.elapsedRealtime.timeUncertaintyNs = static_cast<uint64_t>(elapsedRealtimeUncertaintyNanos);
-
-    return location;
-}
-
-/*
- * GnssCallback class implements the callback methods for IGnss interface.
- */
-struct GnssCallback : public IGnssCallback_V2_1 {
-    Return<void> gnssLocationCb(const GnssLocation_V1_0& location) override;
-    Return<void> gnssStatusCb(const IGnssCallback_V1_0::GnssStatusValue status) override;
-    Return<void> gnssSvStatusCb(const IGnssCallback_V1_0::GnssSvStatus& svStatus) override {
-        return gnssSvStatusCbImpl<IGnssCallback_V1_0::GnssSvStatus, IGnssCallback_V1_0::GnssSvInfo>(
-                svStatus);
-    }
-    Return<void> gnssNmeaCb(int64_t timestamp, const android::hardware::hidl_string& nmea) override;
-    Return<void> gnssSetCapabilitesCb(uint32_t capabilities) override;
-    Return<void> gnssAcquireWakelockCb() override;
-    Return<void> gnssReleaseWakelockCb() override;
-    Return<void> gnssRequestTimeCb() override;
-    Return<void> gnssRequestLocationCb(const bool independentFromGnss) override;
-
-    Return<void> gnssSetSystemInfoCb(const IGnssCallback_V1_0::GnssSystemInfo& info) override;
-
-    // New in 1.1
-    Return<void> gnssNameCb(const android::hardware::hidl_string& name) override;
-
-    // New in 2.0
-    Return<void> gnssRequestLocationCb_2_0(const bool independentFromGnss, const bool isUserEmergency)
-            override;
-    Return<void> gnssSetCapabilitiesCb_2_0(uint32_t capabilities) override;
-    Return<void> gnssLocationCb_2_0(const GnssLocation_V2_0& location) override;
-    Return<void> gnssSvStatusCb_2_0(const hidl_vec<IGnssCallback_V2_0::GnssSvInfo>& svInfoList) override {
-        return gnssSvStatusCbImpl<hidl_vec<IGnssCallback_V2_0::GnssSvInfo>,
-                                  IGnssCallback_V1_0::GnssSvInfo>(svInfoList);
-    }
-
-    // New in 2.1
-    Return<void> gnssSvStatusCb_2_1(const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList) override {
-        return gnssSvStatusCbImpl<hidl_vec<IGnssCallback_V2_1::GnssSvInfo>,
-                                  IGnssCallback_V1_0::GnssSvInfo>(svInfoList);
-    }
-    Return<void> gnssSetCapabilitiesCb_2_1(uint32_t capabilities) override;
-
-    // TODO: Reconsider allocation cost vs threadsafety on these statics
-    static const char* sNmeaString;
-    static size_t sNmeaStringLength;
-
-    template <class T>
-    static Return<void> gnssLocationCbImpl(const T& location);
-
-    template <class T_list, class T_sv_info>
-    static Return<void> gnssSvStatusCbImpl(const T_list& svStatus);
-
-private:
-    template <class T>
-    static uint32_t getHasBasebandCn0DbHzFlag(const T& svStatus) {
-        return 0;
-    }
-
-    template <class T>
-    static double getBasebandCn0DbHz(const T& svStatus, size_t i) {
-        return 0.0;
-    }
-
-    template <class T>
-    static uint32_t getGnssSvInfoListSize(const T& svInfoList) {
-        return svInfoList.size();
-    }
-
-    static const IGnssCallbackAidl::GnssSvInfo& getGnssSvInfoOfIndex(
-            const std::vector<IGnssCallbackAidl::GnssSvInfo>& svInfoList, size_t i) {
-        return svInfoList[i];
-    }
-
-    static const IGnssCallback_V1_0::GnssSvInfo& getGnssSvInfoOfIndex(
-            const IGnssCallback_V1_0::GnssSvStatus& svStatus, size_t i) {
-        return svStatus.gnssSvList.data()[i];
-    }
-
-    static const IGnssCallback_V1_0::GnssSvInfo& getGnssSvInfoOfIndex(
-            const hidl_vec<IGnssCallback_V2_0::GnssSvInfo>& svInfoList, size_t i) {
-        return svInfoList[i].v1_0;
-    }
-
-    static const IGnssCallback_V1_0::GnssSvInfo& getGnssSvInfoOfIndex(
-            const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList, size_t i) {
-        return svInfoList[i].v2_0.v1_0;
-    }
-
-    template <class T>
-    static uint32_t getConstellationType(const T& svInfoList, size_t i) {
-        return static_cast<uint32_t>(svInfoList[i].constellation);
-    }
-};
-
-Return<void> GnssCallback::gnssNameCb(const android::hardware::hidl_string& name) {
-    ALOGD("%s: name=%s\n", __func__, name.c_str());
-
-    JNIEnv* env = getJniEnv();
-    jstring jstringName = env->NewStringUTF(name.c_str());
-    env->CallVoidMethod(mCallbacksObj, method_setGnssHardwareModelName, jstringName);
-    if (jstringName) {
-        env->DeleteLocalRef(jstringName);
-    }
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-
-    return Void();
-}
-
-const char* GnssCallback::sNmeaString = nullptr;
-size_t GnssCallback::sNmeaStringLength = 0;
-
-template<class T>
-Return<void> GnssCallback::gnssLocationCbImpl(const T& location) {
-    JNIEnv* env = getJniEnv();
-
-    jobject jLocation = translateGnssLocation(env, location);
-
-    env->CallVoidMethod(mCallbacksObj,
-                        method_reportLocation,
-                        boolToJbool(hasLatLong(location)),
-                        jLocation);
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    env->DeleteLocalRef(jLocation);
-    return Void();
-}
-
-Return<void> GnssCallback::gnssLocationCb(const GnssLocation_V1_0& location) {
-    return gnssLocationCbImpl<GnssLocation_V1_0>(location);
-}
-
-Return<void>
-GnssCallback::gnssLocationCb_2_0(const GnssLocation_V2_0& location) {
-    return gnssLocationCbImpl<GnssLocation_V2_0>(location);
-}
-
-Return<void> GnssCallback::gnssStatusCb(const IGnssCallback_V2_0::GnssStatusValue status) {
-    JNIEnv* env = getJniEnv();
-    env->CallVoidMethod(mCallbacksObj, method_reportStatus, status);
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return Void();
-}
-
-template<>
-uint32_t GnssCallback::getHasBasebandCn0DbHzFlag(const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>&
-        svStatus) {
-    return SVID_FLAGS_HAS_BASEBAND_CN0;
-}
-
-template <>
-uint32_t GnssCallback::getHasBasebandCn0DbHzFlag(
-        const std::vector<IGnssCallbackAidl::GnssSvInfo>& svStatus) {
-    return SVID_FLAGS_HAS_BASEBAND_CN0;
-}
-
-template <>
-double GnssCallback::getBasebandCn0DbHz(
-        const std::vector<IGnssCallbackAidl::GnssSvInfo>& svInfoList, size_t i) {
-    return svInfoList[i].basebandCN0DbHz;
-}
-
-template<>
-double GnssCallback::getBasebandCn0DbHz(const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList,
-        size_t i) {
-    return svInfoList[i].basebandCN0DbHz;
-}
-
-template <>
-uint32_t GnssCallback::getGnssSvInfoListSize(const IGnssCallback_V1_0::GnssSvStatus& svStatus) {
-    return svStatus.numSvs;
-}
-
-template <>
-uint32_t GnssCallback::getConstellationType(const IGnssCallback_V1_0::GnssSvStatus& svStatus,
-                                            size_t i) {
-    return static_cast<uint32_t>(svStatus.gnssSvList.data()[i].constellation);
-}
-
-template <>
-uint32_t GnssCallback::getConstellationType(
-        const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList, size_t i) {
-    return static_cast<uint32_t>(svInfoList[i].v2_0.constellation);
-}
-
-template <class T_list, class T_sv_info>
-Return<void> GnssCallback::gnssSvStatusCbImpl(const T_list& svStatus) {
-    // In HIDL or AIDL v1, if no listener is registered, do not report svInfoList to the framework.
-    if (gnssHalAidl == nullptr || gnssHalAidl->getInterfaceVersion() <= 1) {
-        if (!isSvStatusRegistered) {
-            return Void();
-        }
-    }
-
-    JNIEnv* env = getJniEnv();
-
-    uint32_t listSize = getGnssSvInfoListSize(svStatus);
-
-    jintArray svidWithFlagArray = env->NewIntArray(listSize);
-    jfloatArray cn0Array = env->NewFloatArray(listSize);
-    jfloatArray elevArray = env->NewFloatArray(listSize);
-    jfloatArray azimArray = env->NewFloatArray(listSize);
-    jfloatArray carrierFreqArray = env->NewFloatArray(listSize);
-    jfloatArray basebandCn0Array = env->NewFloatArray(listSize);
-
-    jint* svidWithFlags = env->GetIntArrayElements(svidWithFlagArray, 0);
-    jfloat* cn0s = env->GetFloatArrayElements(cn0Array, 0);
-    jfloat* elev = env->GetFloatArrayElements(elevArray, 0);
-    jfloat* azim = env->GetFloatArrayElements(azimArray, 0);
-    jfloat* carrierFreq = env->GetFloatArrayElements(carrierFreqArray, 0);
-    jfloat* basebandCn0s = env->GetFloatArrayElements(basebandCn0Array, 0);
-
-    /*
-     * Read GNSS SV info.
-     */
-    for (size_t i = 0; i < listSize; ++i) {
-        enum ShiftWidth: uint8_t {
-            SVID_SHIFT_WIDTH = 12,
-            CONSTELLATION_TYPE_SHIFT_WIDTH = 8
-        };
-
-        const T_sv_info& info = getGnssSvInfoOfIndex(svStatus, i);
-        svidWithFlags[i] = (info.svid << SVID_SHIFT_WIDTH) |
-            (getConstellationType(svStatus, i) << CONSTELLATION_TYPE_SHIFT_WIDTH) |
-            static_cast<uint32_t>(info.svFlag);
-        cn0s[i] = info.cN0Dbhz;
-        elev[i] = info.elevationDegrees;
-        azim[i] = info.azimuthDegrees;
-        carrierFreq[i] = info.carrierFrequencyHz;
-        svidWithFlags[i] |= getHasBasebandCn0DbHzFlag(svStatus);
-        basebandCn0s[i] = getBasebandCn0DbHz(svStatus, i);
-    }
-
-    env->ReleaseIntArrayElements(svidWithFlagArray, svidWithFlags, 0);
-    env->ReleaseFloatArrayElements(cn0Array, cn0s, 0);
-    env->ReleaseFloatArrayElements(elevArray, elev, 0);
-    env->ReleaseFloatArrayElements(azimArray, azim, 0);
-    env->ReleaseFloatArrayElements(carrierFreqArray, carrierFreq, 0);
-    env->ReleaseFloatArrayElements(basebandCn0Array, basebandCn0s, 0);
-
-    env->CallVoidMethod(mCallbacksObj, method_reportSvStatus,
-            static_cast<jint>(listSize), svidWithFlagArray, cn0Array, elevArray, azimArray,
-            carrierFreqArray, basebandCn0Array);
-
-    env->DeleteLocalRef(svidWithFlagArray);
-    env->DeleteLocalRef(cn0Array);
-    env->DeleteLocalRef(elevArray);
-    env->DeleteLocalRef(azimArray);
-    env->DeleteLocalRef(carrierFreqArray);
-    env->DeleteLocalRef(basebandCn0Array);
-
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return Void();
-}
-
-Return<void> GnssCallback::gnssNmeaCb(int64_t timestamp,
-                                      const ::android::hardware::hidl_string& nmea) {
-    // In HIDL, if no listener is registered, do not report nmea to the framework.
-    if (!isNmeaRegistered) {
-        return Void();
-    }
-    JNIEnv* env = getJniEnv();
-    /*
-     * The Java code will call back to read these values.
-     * We do this to avoid creating unnecessary String objects.
-     */
-    sNmeaString = nmea.c_str();
-    sNmeaStringLength = nmea.size();
-
-    env->CallVoidMethod(mCallbacksObj, method_reportNmea, timestamp);
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return Void();
-}
-
-Return<void> GnssCallback::gnssSetCapabilitesCb(uint32_t capabilities) {
-    ALOGD("%s: %du\n", __func__, capabilities);
-
-    JNIEnv* env = getJniEnv();
-    env->CallVoidMethod(mCallbacksObj, method_setTopHalCapabilities, capabilities);
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return Void();
-}
-
-Return<void> GnssCallback::gnssSetCapabilitiesCb_2_0(uint32_t capabilities) {
-    return GnssCallback::gnssSetCapabilitesCb(capabilities);
-}
-
-Return<void> GnssCallback::gnssSetCapabilitiesCb_2_1(uint32_t capabilities) {
-    return GnssCallback::gnssSetCapabilitesCb(capabilities);
-}
-
-Return<void> GnssCallback::gnssAcquireWakelockCb() {
-    acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_NAME);
-    return Void();
-}
-
-Return<void> GnssCallback::gnssReleaseWakelockCb() {
-    release_wake_lock(WAKE_LOCK_NAME);
-    return Void();
-}
-
-Return<void> GnssCallback::gnssRequestTimeCb() {
-    JNIEnv* env = getJniEnv();
-    env->CallVoidMethod(mCallbacksObj, method_requestUtcTime);
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return Void();
-}
-
-Return<void> GnssCallback::gnssRequestLocationCb(const bool independentFromGnss) {
-    return GnssCallback::gnssRequestLocationCb_2_0(independentFromGnss, /* isUserEmergency= */
-            false);
-}
-
-Return<void> GnssCallback::gnssRequestLocationCb_2_0(const bool independentFromGnss, const bool
-        isUserEmergency) {
-    JNIEnv* env = getJniEnv();
-    env->CallVoidMethod(mCallbacksObj, method_requestLocation, boolToJbool(independentFromGnss),
-            boolToJbool(isUserEmergency));
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return Void();
-}
-
-Return<void> GnssCallback::gnssSetSystemInfoCb(const IGnssCallback_V2_0::GnssSystemInfo& info) {
-    ALOGD("%s: yearOfHw=%d\n", __func__, info.yearOfHw);
-
-    JNIEnv* env = getJniEnv();
-    env->CallVoidMethod(mCallbacksObj, method_setGnssYearOfHardware,
-                        info.yearOfHw);
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return Void();
-}
-
-class GnssCallbackAidl : public android::hardware::gnss::BnGnssCallback {
-public:
-    Status gnssSetCapabilitiesCb(const int capabilities) override;
-    Status gnssStatusCb(const GnssStatusValue status) override;
-    Status gnssSvStatusCb(const std::vector<GnssSvInfo>& svInfoList) override;
-    Status gnssLocationCb(const GnssLocationAidl& location) override;
-    Status gnssNmeaCb(const int64_t timestamp, const std::string& nmea) override;
-    Status gnssAcquireWakelockCb() override;
-    Status gnssReleaseWakelockCb() override;
-    Status gnssSetSystemInfoCb(const GnssSystemInfo& info) override;
-    Status gnssRequestTimeCb() override;
-    Status gnssRequestLocationCb(const bool independentFromGnss,
-                                 const bool isUserEmergency) override;
-};
-
-Status GnssCallbackAidl::gnssSetCapabilitiesCb(const int capabilities) {
-    ALOGD("GnssCallbackAidl::%s: %du\n", __func__, capabilities);
-    JNIEnv* env = getJniEnv();
-    env->CallVoidMethod(mCallbacksObj, method_setTopHalCapabilities, capabilities);
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return Status::ok();
-}
-
-Status GnssCallbackAidl::gnssStatusCb(const GnssStatusValue status) {
-    JNIEnv* env = getJniEnv();
-    env->CallVoidMethod(mCallbacksObj, method_reportStatus, status);
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return Status::ok();
-}
-
-Status GnssCallbackAidl::gnssSvStatusCb(const std::vector<GnssSvInfo>& svInfoList) {
-    GnssCallback::gnssSvStatusCbImpl<std::vector<GnssSvInfo>, GnssSvInfo>(svInfoList);
-    return Status::ok();
-}
-
-Status GnssCallbackAidl::gnssLocationCb(const GnssLocationAidl& location) {
-    GnssCallback::gnssLocationCbImpl<GnssLocationAidl>(location);
-    return Status::ok();
-}
-
-Status GnssCallbackAidl::gnssNmeaCb(const int64_t timestamp, const std::string& nmea) {
-    // In AIDL v1, if no listener is registered, do not report nmea to the framework.
-    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() <= 1) {
-        if (!isNmeaRegistered) {
-            return Status::ok();
-        }
-    }
-    JNIEnv* env = getJniEnv();
-    /*
-     * The Java code will call back to read these values.
-     * We do this to avoid creating unnecessary String objects.
-     */
-    GnssCallback::sNmeaString = nmea.c_str();
-    GnssCallback::sNmeaStringLength = nmea.size();
-
-    env->CallVoidMethod(mCallbacksObj, method_reportNmea, timestamp);
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return Status::ok();
-}
-
-Status GnssCallbackAidl::gnssAcquireWakelockCb() {
-    acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_NAME);
-    return Status::ok();
-}
-
-Status GnssCallbackAidl::gnssReleaseWakelockCb() {
-    release_wake_lock(WAKE_LOCK_NAME);
-    return Status::ok();
-}
-
-Status GnssCallbackAidl::gnssSetSystemInfoCb(const GnssSystemInfo& info) {
-    ALOGD("%s: yearOfHw=%d, name=%s\n", __func__, info.yearOfHw, info.name.c_str());
-    JNIEnv* env = getJniEnv();
-    env->CallVoidMethod(mCallbacksObj, method_setGnssYearOfHardware, info.yearOfHw);
-    jstring jstringName = env->NewStringUTF(info.name.c_str());
-    env->CallVoidMethod(mCallbacksObj, method_setGnssHardwareModelName, jstringName);
-    if (jstringName) {
-        env->DeleteLocalRef(jstringName);
-    }
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return Status::ok();
-}
-
-Status GnssCallbackAidl::gnssRequestTimeCb() {
-    JNIEnv* env = getJniEnv();
-    env->CallVoidMethod(mCallbacksObj, method_requestUtcTime);
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return Status::ok();
-}
-
-Status GnssCallbackAidl::gnssRequestLocationCb(const bool independentFromGnss,
-                                               const bool isUserEmergency) {
-    JNIEnv* env = getJniEnv();
-    env->CallVoidMethod(mCallbacksObj, method_requestLocation, boolToJbool(independentFromGnss),
-                        boolToJbool(isUserEmergency));
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return Status::ok();
-}
-
 /*
  * GnssPowerIndicationCallback class implements the callback methods for the IGnssPowerIndication
  * interface.
@@ -756,35 +196,6 @@
 }
 
 /*
- * GnssPsdsCallback class implements the callback methods for the IGnssPsds
- * interface.
- */
-struct GnssPsdsCallbackAidl : public android::hardware::gnss::BnGnssPsdsCallback {
-    Status downloadRequestCb(PsdsType psdsType) override {
-        ALOGD("%s. psdsType: %d", __func__, static_cast<int32_t>(psdsType));
-        JNIEnv* env = getJniEnv();
-        env->CallVoidMethod(mCallbacksObj, method_psdsDownloadRequest, psdsType);
-        checkAndClearExceptionFromCallback(env, __FUNCTION__);
-        return Status::ok();
-    }
-};
-
-/**
- * GnssXtraCallback class implements the callback methods for the IGnssXtra
- * interface.
- */
-class GnssXtraCallback : public IGnssXtraCallback {
-    Return<void> downloadRequestCb() override;
-};
-
-Return<void> GnssXtraCallback::downloadRequestCb() {
-    JNIEnv* env = getJniEnv();
-    env->CallVoidMethod(mCallbacksObj, method_psdsDownloadRequest, /* psdsType= */ 1);
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return Void();
-}
-
-/*
  * GnssNiCallback implements callback methods required by the IGnssNi interface.
  */
 struct GnssNiCallback : public IGnssNiCallback {
@@ -822,41 +233,7 @@
 
 /* Initializes the GNSS service handle. */
 static void android_location_gnss_hal_GnssNative_set_gps_service_handle() {
-    gnssHalAidl = waitForVintfService<IGnssAidl>();
-    if (gnssHalAidl != nullptr) {
-        ALOGD("Successfully got GNSS AIDL handle. Version=%d.", gnssHalAidl->getInterfaceVersion());
-        if (gnssHalAidl->getInterfaceVersion() >= 2) {
-            return;
-        }
-    }
-
-    ALOGD("Trying IGnss_V2_1::getService()");
-    gnssHal_V2_1 = IGnss_V2_1::getService();
-    if (gnssHal_V2_1 != nullptr) {
-        gnssHal = gnssHal_V2_1;
-        gnssHal_V2_0 = gnssHal_V2_1;
-        gnssHal_V1_1 = gnssHal_V2_1;
-        gnssHal = gnssHal_V2_1;
-        return;
-    }
-
-    ALOGD("gnssHal 2.1 was null, trying 2.0");
-    gnssHal_V2_0 = IGnss_V2_0::getService();
-    if (gnssHal_V2_0 != nullptr) {
-        gnssHal = gnssHal_V2_0;
-        gnssHal_V1_1 = gnssHal_V2_0;
-        return;
-    }
-
-    ALOGD("gnssHal 2.0 was null, trying 1.1");
-    gnssHal_V1_1 = IGnss_V1_1::getService();
-    if (gnssHal_V1_1 != nullptr) {
-        gnssHal = gnssHal_V1_1;
-        return;
-    }
-
-    ALOGD("gnssHal 1.1 was null, trying 1.0");
-    gnssHal = IGnss_V1_0::getService();
+    gnssHal = std::make_unique<gnss::GnssHal>();
 }
 
 /* One time initialization at system boot */
@@ -865,21 +242,10 @@
     android_location_gnss_hal_GnssNative_set_gps_service_handle();
 
     // Cache methodIDs and class IDs.
-    method_reportLocation = env->GetMethodID(clazz, "reportLocation",
-            "(ZLandroid/location/Location;)V");
-    method_reportStatus = env->GetMethodID(clazz, "reportStatus", "(I)V");
-    method_reportSvStatus = env->GetMethodID(clazz, "reportSvStatus", "(I[I[F[F[F[F[F)V");
-    method_reportNmea = env->GetMethodID(clazz, "reportNmea", "(J)V");
-    method_setTopHalCapabilities = env->GetMethodID(clazz, "setTopHalCapabilities", "(I)V");
-    method_setGnssYearOfHardware = env->GetMethodID(clazz, "setGnssYearOfHardware", "(I)V");
-    method_setGnssHardwareModelName = env->GetMethodID(clazz, "setGnssHardwareModelName",
-            "(Ljava/lang/String;)V");
-    method_psdsDownloadRequest = env->GetMethodID(clazz, "psdsDownloadRequest", "(I)V");
+
     method_reportNiNotification = env->GetMethodID(clazz, "reportNiNotification",
             "(IIIIILjava/lang/String;Ljava/lang/String;II)V");
-    method_requestLocation = env->GetMethodID(clazz, "requestLocation", "(ZZ)V");
-    method_requestUtcTime = env->GetMethodID(clazz, "requestUtcTime", "()V");
-    method_reportGnssServiceDied = env->GetMethodID(clazz, "reportGnssServiceDied", "()V");
+
     method_reportNfwNotification = env->GetMethodID(clazz, "reportNfwNotification",
             "(Ljava/lang/String;BLjava/lang/String;BLjava/lang/String;BZZ)V");
     method_reportGnssPowerStats =
@@ -894,17 +260,19 @@
     class_gnssPowerStats = (jclass)env->NewGlobalRef(gnssPowerStatsClass);
     method_gnssPowerStatsCtor = env->GetMethodID(class_gnssPowerStats, "<init>", "(IJDDDDDD[D)V");
 
+    gnss::AGnss_class_init_once(env, clazz);
+    gnss::AGnssRil_class_init_once(env, clazz);
+    gnss::Gnss_class_init_once(env, clazz);
     gnss::GnssAntennaInfo_class_init_once(env, clazz);
     gnss::GnssBatching_class_init_once(env, clazz);
     gnss::GnssConfiguration_class_init_once(env);
     gnss::GnssGeofence_class_init_once(env, clazz);
     gnss::GnssMeasurement_class_init_once(env, clazz);
     gnss::GnssNavigationMessage_class_init_once(env, clazz);
+    gnss::GnssPsds_class_init_once(env, clazz);
     gnss::GnssVisibilityControl_class_init_once(env, clazz);
     gnss::MeasurementCorrections_class_init_once(env, clazz);
     gnss::MeasurementCorrectionsCallback_class_init_once(env, clazz);
-    gnss::AGnss_class_init_once(env, clazz);
-    gnss::AGnssRil_class_init_once(env, clazz);
     gnss::Utils_class_init_once(env);
 }
 
@@ -923,313 +291,26 @@
         android_location_gnss_hal_GnssNative_set_gps_service_handle();
     }
 
-    if (gnssHal == nullptr && gnssHalAidl == nullptr) {
+    if (gnssHal == nullptr || !gnssHal->isSupported()) {
         ALOGE("Unable to get GPS service\n");
         return;
     }
 
-    // TODO: linkToDeath for AIDL HAL
-
-    if (gnssHal != nullptr) {
-        gnssHalDeathRecipient = new GnssDeathRecipient();
-        hardware::Return<bool> linked = gnssHal->linkToDeath(gnssHalDeathRecipient, /*cookie*/ 0);
-        if (!linked.isOk()) {
-            ALOGE("Transaction error in linking to GnssHAL death: %s",
-                  linked.description().c_str());
-        } else if (!linked) {
-            ALOGW("Unable to link to GnssHal death notifications");
-        } else {
-            ALOGD("Link to death notification successful");
-        }
-    }
-
-    if (gnssHalAidl != nullptr) {
-        sp<IGnssPsdsAidl> gnssPsdsAidl;
-        auto status = gnssHalAidl->getExtensionPsds(&gnssPsdsAidl);
-        if (status.isOk()) {
-            gnssPsdsAidlIface = gnssPsdsAidl;
-        } else {
-            ALOGD("Unable to get a handle to PSDS AIDL interface.");
-        }
-    } else if (gnssHal != nullptr) {
-        auto gnssXtra = gnssHal->getExtensionXtra();
-        if (!gnssXtra.isOk()) {
-            ALOGD("Unable to get a handle to Xtra");
-        } else {
-            gnssXtraIface = gnssXtra;
-        }
-    }
-
-    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
-        sp<IAGnssRilAidl> agnssRilAidl;
-        auto status = gnssHalAidl->getExtensionAGnssRil(&agnssRilAidl);
-        if (checkAidlStatus(status, "Unable to get a handle to AGnssRil interface.")) {
-            agnssRilIface = std::make_unique<gnss::AGnssRil>(agnssRilAidl);
-        }
-    } else if (gnssHal_V2_0 != nullptr) {
-        auto agnssRil_V2_0 = gnssHal_V2_0->getExtensionAGnssRil_2_0();
-        if (checkHidlReturn(agnssRil_V2_0, "Unable to get a handle to AGnssRil_V2_0")) {
-            agnssRilIface = std::make_unique<gnss::AGnssRil_V2_0>(agnssRil_V2_0);
-        }
-    } else if (gnssHal != nullptr) {
-        auto agnssRil_V1_0 = gnssHal->getExtensionAGnssRil();
-        if (checkHidlReturn(agnssRil_V1_0, "Unable to get a handle to AGnssRil_V1_0")) {
-            agnssRilIface = std::make_unique<gnss::AGnssRil_V1_0>(agnssRil_V1_0);
-        }
-    }
-
-    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
-        sp<IAGnssAidl> agnssAidl;
-        auto status = gnssHalAidl->getExtensionAGnss(&agnssAidl);
-        if (checkAidlStatus(status, "Unable to get a handle to AGnss interface.")) {
-            agnssIface = std::make_unique<gnss::AGnss>(agnssAidl);
-        }
-    } else if (gnssHal_V2_0 != nullptr) {
-        auto agnss_V2_0 = gnssHal_V2_0->getExtensionAGnss_2_0();
-        if (checkHidlReturn(agnss_V2_0, "Unable to get a handle to AGnss_V2_0")) {
-            agnssIface = std::make_unique<gnss::AGnss_V2_0>(agnss_V2_0);
-        }
-    } else if (gnssHal != nullptr) {
-        auto agnss_V1_0 = gnssHal->getExtensionAGnss();
-        if (checkHidlReturn(agnss_V1_0, "Unable to get a handle to AGnss_V1_0")) {
-            agnssIface = std::make_unique<gnss::AGnss_V1_0>(agnss_V1_0);
-        }
-    }
-
-    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
-        sp<hardware::gnss::IGnssNavigationMessageInterface> gnssNavigationMessage;
-        auto status = gnssHalAidl->getExtensionGnssNavigationMessage(&gnssNavigationMessage);
-        if (checkAidlStatus(status,
-                            "Unable to get a handle to GnssNavigationMessage AIDL interface.")) {
-            gnssNavigationMessageIface =
-                    std::make_unique<gnss::GnssNavigationMessageAidl>(gnssNavigationMessage);
-        }
-    } else if (gnssHal != nullptr) {
-        auto gnssNavigationMessage = gnssHal->getExtensionGnssNavigationMessage();
-        if (checkHidlReturn(gnssNavigationMessage,
-                            "Unable to get a handle to GnssNavigationMessage interface.")) {
-            gnssNavigationMessageIface =
-                    std::make_unique<gnss::GnssNavigationMessageHidl>(gnssNavigationMessage);
-        }
-    }
-
-    // Allow all causal combinations between IGnss.hal and IGnssMeasurement.hal. That means,
-    // 2.1@IGnss can be paired with {1.0, 1,1, 2.0, 2.1}@IGnssMeasurement
-    // 2.0@IGnss can be paired with {1.0, 1,1, 2.0}@IGnssMeasurement
-    // 1.1@IGnss can be paired {1.0, 1.1}@IGnssMeasurement
-    // 1.0@IGnss is paired with 1.0@IGnssMeasurement
-    gnssMeasurementIface = nullptr;
-    if (gnssHalAidl != nullptr) {
-        sp<hardware::gnss::IGnssMeasurementInterface> gnssMeasurement;
-        auto status = gnssHalAidl->getExtensionGnssMeasurement(&gnssMeasurement);
-        if (checkAidlStatus(status, "Unable to get a handle to GnssMeasurement AIDL interface.")) {
-            gnssMeasurementIface =
-                    std::make_unique<android::gnss::GnssMeasurement>(gnssMeasurement);
-        }
-    }
-    if (gnssHal_V2_1 != nullptr && gnssMeasurementIface == nullptr) {
-        auto gnssMeasurement = gnssHal_V2_1->getExtensionGnssMeasurement_2_1();
-        if (checkHidlReturn(gnssMeasurement, "Unable to get a handle to GnssMeasurement_V2_1")) {
-            gnssMeasurementIface =
-                    std::make_unique<android::gnss::GnssMeasurement_V2_1>(gnssMeasurement);
-        }
-    }
-    if (gnssHal_V2_0 != nullptr && gnssMeasurementIface == nullptr) {
-        auto gnssMeasurement = gnssHal_V2_0->getExtensionGnssMeasurement_2_0();
-        if (checkHidlReturn(gnssMeasurement, "Unable to get a handle to GnssMeasurement_V2_0")) {
-            gnssMeasurementIface =
-                    std::make_unique<android::gnss::GnssMeasurement_V2_0>(gnssMeasurement);
-        }
-    }
-    if (gnssHal_V1_1 != nullptr && gnssMeasurementIface == nullptr) {
-        auto gnssMeasurement = gnssHal_V1_1->getExtensionGnssMeasurement_1_1();
-        if (checkHidlReturn(gnssMeasurement, "Unable to get a handle to GnssMeasurement_V1_1")) {
-            gnssMeasurementIface =
-                    std::make_unique<android::gnss::GnssMeasurement_V1_1>(gnssMeasurement);
-        }
-    }
-    if (gnssHal != nullptr && gnssMeasurementIface == nullptr) {
-        auto gnssMeasurement = gnssHal->getExtensionGnssMeasurement();
-        if (checkHidlReturn(gnssMeasurement, "Unable to get a handle to GnssMeasurement_V1_0")) {
-            gnssMeasurementIface =
-                    std::make_unique<android::gnss::GnssMeasurement_V1_0>(gnssMeasurement);
-        }
-    }
-
-    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
-        sp<IGnssAntennaInfoAidl> gnssAntennaInfoAidl;
-        auto status = gnssHalAidl->getExtensionGnssAntennaInfo(&gnssAntennaInfoAidl);
-        if (checkAidlStatus(status, "Unable to get a handle to GnssAntennaInfo interface.")) {
-            gnssAntennaInfoIface = std::make_unique<gnss::GnssAntennaInfoAidl>(gnssAntennaInfoAidl);
-        }
-    } else if (gnssHal_V2_1 != nullptr) {
-        auto gnssAntennaInfo_V2_1 = gnssHal_V2_1->getExtensionGnssAntennaInfo();
-        if (checkHidlReturn(gnssAntennaInfo_V2_1,
-                            "Unable to get a handle to GnssAntennaInfo_V2_1")) {
-            gnssAntennaInfoIface =
-                    std::make_unique<gnss::GnssAntennaInfo_V2_1>(gnssAntennaInfo_V2_1);
-        }
-    }
-
-    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
-        sp<android::hardware::gnss::measurement_corrections::IMeasurementCorrectionsInterface>
-                gnssMeasurementCorrectionsAidl;
-        auto status =
-                gnssHalAidl->getExtensionMeasurementCorrections(&gnssMeasurementCorrectionsAidl);
-        if (checkAidlStatus(status,
-                            "Unable to get a handle to GnssVisibilityControl AIDL interface.")) {
-            gnssMeasurementCorrectionsIface =
-                    std::make_unique<gnss::MeasurementCorrectionsIface_Aidl>(
-                            gnssMeasurementCorrectionsAidl);
-        }
-    }
-    if (gnssHal_V2_1 != nullptr && gnssMeasurementCorrectionsIface == nullptr) {
-        auto gnssCorrections = gnssHal_V2_1->getExtensionMeasurementCorrections_1_1();
-        if (checkHidlReturn(gnssCorrections,
-                            "Unable to get a handle to GnssMeasurementCorrections HIDL "
-                            "interface")) {
-            gnssMeasurementCorrectionsIface =
-                    std::make_unique<gnss::MeasurementCorrectionsIface_V1_1>(gnssCorrections);
-        }
-    }
-    if (gnssHal_V2_0 != nullptr && gnssMeasurementCorrectionsIface == nullptr) {
-        auto gnssCorrections = gnssHal_V2_0->getExtensionMeasurementCorrections();
-        if (checkHidlReturn(gnssCorrections,
-                            "Unable to get a handle to GnssMeasurementCorrections HIDL "
-                            "interface")) {
-            gnssMeasurementCorrectionsIface =
-                    std::make_unique<gnss::MeasurementCorrectionsIface_V1_0>(gnssCorrections);
-        }
-    }
-
-    // Allow all causal combinations between IGnss.hal and IGnssDebug.hal. That means,
-    // 2.0@IGnss can be paired with {1.0, 2.0}@IGnssDebug
-    // 1.0@IGnss is paired with 1.0@IGnssDebug
-
-    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
-        sp<IGnssDebugAidl> gnssDebugAidl;
-        auto status = gnssHalAidl->getExtensionGnssDebug(&gnssDebugAidl);
-        if (checkAidlStatus(status, "Unable to get a handle to GnssDebug interface.")) {
-            gnssDebugIface = std::make_unique<gnss::GnssDebug>(gnssDebugAidl);
-        }
-    }
-    if (gnssHal_V2_0 != nullptr && gnssDebugIface == nullptr) {
-        auto gnssDebug_V2_0 = gnssHal_V2_0->getExtensionGnssDebug_2_0();
-        if (checkHidlReturn(gnssDebug_V2_0, "Unable to get a handle to GnssDebug_V2_0.")) {
-            gnssDebugIface = std::make_unique<gnss::GnssDebug_V2_0>(gnssDebug_V2_0);
-        }
-    }
-    if (gnssHal != nullptr && gnssDebugIface == nullptr) {
-        auto gnssDebug_V1_0 = gnssHal->getExtensionGnssDebug();
-        if (checkHidlReturn(gnssDebug_V1_0, "Unable to get a handle to GnssDebug_V1_0.")) {
-            gnssDebugIface = std::make_unique<gnss::GnssDebug_V1_0>(gnssDebug_V1_0);
-        }
-    }
-
-    if (gnssHal != nullptr) {
-        auto gnssNi = gnssHal->getExtensionGnssNi();
-        if (!gnssNi.isOk()) {
-            ALOGD("Unable to get a handle to GnssNi");
-        } else {
-            gnssNiIface = gnssNi;
-        }
-    }
-
-    if (gnssHalAidl != nullptr) {
-        sp<IGnssConfigurationAidl> gnssConfigurationAidl;
-        auto status = gnssHalAidl->getExtensionGnssConfiguration(&gnssConfigurationAidl);
-        if (checkAidlStatus(status,
-                            "Unable to get a handle to GnssConfiguration AIDL interface.")) {
-            gnssConfigurationIface =
-                    std::make_unique<android::gnss::GnssConfiguration>(gnssConfigurationAidl);
-        }
-    } else if (gnssHal_V2_1 != nullptr) {
-        auto gnssConfiguration = gnssHal_V2_1->getExtensionGnssConfiguration_2_1();
-        if (checkHidlReturn(gnssConfiguration,
-                            "Unable to get a handle to GnssConfiguration_V2_1")) {
-            gnssConfigurationIface =
-                    std::make_unique<android::gnss::GnssConfiguration_V2_1>(gnssConfiguration);
-        }
-    } else if (gnssHal_V2_0 != nullptr) {
-        auto gnssConfiguration = gnssHal_V2_0->getExtensionGnssConfiguration_2_0();
-        if (checkHidlReturn(gnssConfiguration,
-                            "Unable to get a handle to GnssConfiguration_V2_0")) {
-            gnssConfigurationIface =
-                    std::make_unique<android::gnss::GnssConfiguration_V2_0>(gnssConfiguration);
-        }
-    } else if (gnssHal_V1_1 != nullptr) {
-        auto gnssConfiguration = gnssHal_V1_1->getExtensionGnssConfiguration_1_1();
-        if (checkHidlReturn(gnssConfiguration,
-                            "Unable to get a handle to GnssConfiguration_V1_1")) {
-            gnssConfigurationIface =
-                    std::make_unique<gnss::GnssConfiguration_V1_1>(gnssConfiguration);
-        }
-    } else if (gnssHal != nullptr) {
-        auto gnssConfiguration = gnssHal->getExtensionGnssConfiguration();
-        if (checkHidlReturn(gnssConfiguration,
-                            "Unable to get a handle to GnssConfiguration_V1_0")) {
-            gnssConfigurationIface =
-                    std::make_unique<gnss::GnssConfiguration_V1_0>(gnssConfiguration);
-        }
-    }
-
-    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
-        sp<hardware::gnss::IGnssGeofence> gnssGeofence;
-        auto status = gnssHalAidl->getExtensionGnssGeofence(&gnssGeofence);
-        if (checkAidlStatus(status, "Unable to get a handle to GnssGeofence AIDL interface.")) {
-            gnssGeofencingIface = std::make_unique<gnss::GnssGeofenceAidl>(gnssGeofence);
-        }
-    } else if (gnssHal != nullptr) {
-        auto gnssGeofencing = gnssHal->getExtensionGnssGeofencing();
-        if (checkHidlReturn(gnssGeofencing, "Unable to get a handle to GnssGeofencing")) {
-            gnssGeofencingIface = std::make_unique<gnss::GnssGeofenceHidl>(gnssGeofencing);
-        }
-    }
-
-    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
-        sp<android::hardware::gnss::IGnssBatching> gnssBatchingAidl;
-        auto status = gnssHalAidl->getExtensionGnssBatching(&gnssBatchingAidl);
-        if (checkAidlStatus(status, "Unable to get a handle to GnssBatching interface.")) {
-            gnssBatchingIface = std::make_unique<gnss::GnssBatching>(gnssBatchingAidl);
-        }
-    } else if (gnssHal_V2_0 != nullptr) {
-        auto gnssBatching_V2_0 = gnssHal_V2_0->getExtensionGnssBatching_2_0();
-        if (checkHidlReturn(gnssBatching_V2_0, "Unable to get a handle to GnssBatching_V2_0")) {
-            gnssBatchingIface = std::make_unique<gnss::GnssBatching_V2_0>(gnssBatching_V2_0);
-        }
-    }
-    if (gnssHal != nullptr && gnssBatchingIface == nullptr) {
-        auto gnssBatching_V1_0 = gnssHal->getExtensionGnssBatching();
-        if (checkHidlReturn(gnssBatching_V1_0, "Unable to get a handle to GnssBatching")) {
-            gnssBatchingIface = std::make_unique<gnss::GnssBatching_V1_0>(gnssBatching_V1_0);
-        }
-    }
-
-    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
-        sp<android::hardware::gnss::visibility_control::IGnssVisibilityControl>
-                gnssVisibilityControlAidl;
-        auto status = gnssHalAidl->getExtensionGnssVisibilityControl(&gnssVisibilityControlAidl);
-        if (checkAidlStatus(status,
-                            "Unable to get a handle to GnssVisibilityControl AIDL interface.")) {
-            gnssVisibilityControlIface =
-                    std::make_unique<gnss::GnssVisibilityControlAidl>(gnssVisibilityControlAidl);
-        }
-    } else if (gnssHal_V2_0 != nullptr) {
-        auto gnssVisibilityControlHidl = gnssHal_V2_0->getExtensionVisibilityControl();
-        if (checkHidlReturn(gnssVisibilityControlHidl,
-                            "Unable to get a handle to GnssVisibilityControl HIDL interface")) {
-            gnssVisibilityControlIface =
-                    std::make_unique<gnss::GnssVisibilityControlHidl>(gnssVisibilityControlHidl);
-        }
-    }
-
-    if (gnssHalAidl != nullptr) {
-        sp<IGnssPowerIndication> gnssPowerIndication;
-        auto status = gnssHalAidl->getExtensionGnssPowerIndication(&gnssPowerIndication);
-        if (checkAidlStatus(status, "Unable to get a handle to GnssPowerIndication interface.")) {
-            gnssPowerIndicationIface = gnssPowerIndication;
-        }
-    }
+    gnssHal->linkToDeath();
+    gnssPsdsIface = gnssHal->getGnssPsdsInterface();
+    agnssRilIface = gnssHal->getAGnssRilInterface();
+    agnssIface = gnssHal->getAGnssInterface();
+    gnssNavigationMessageIface = gnssHal->getGnssNavigationMessageInterface();
+    gnssMeasurementIface = gnssHal->getGnssMeasurementInterface();
+    gnssAntennaInfoIface = gnssHal->getGnssAntennaInfoInterface();
+    gnssMeasurementCorrectionsIface = gnssHal->getMeasurementCorrectionsInterface();
+    gnssDebugIface = gnssHal->getGnssDebugInterface();
+    gnssNiIface = gnssHal->getGnssNiInterface();
+    gnssConfigurationIface = gnssHal->getGnssConfigurationInterface();
+    gnssGeofencingIface = gnssHal->getGnssGeofenceInterface();
+    gnssBatchingIface = gnssHal->getGnssBatchingInterface();
+    gnssVisibilityControlIface = gnssHal->getGnssVisibilityControlInterface();
+    gnssPowerIndicationIface = gnssHal->getGnssPowerIndicationInterface();
 
     if (mCallbacksObj) {
         ALOGE("Callbacks already initialized");
@@ -1239,7 +320,7 @@
 }
 
 static jboolean android_location_gnss_hal_GnssNative_is_supported(JNIEnv* /* env */, jclass) {
-    return (gnssHalAidl != nullptr || gnssHal != nullptr) ? JNI_TRUE : JNI_FALSE;
+    return (gnssHal != nullptr && gnssHal->isSupported()) ? JNI_TRUE : JNI_FALSE;
 }
 
 static jboolean android_location_GnssNetworkConnectivityHandler_is_agps_ril_supported(
@@ -1268,52 +349,18 @@
     /*
      * Fail if the main interface fails to initialize
      */
-    if (gnssHal == nullptr && gnssHalAidl == nullptr) {
+    if (!gnssHal->isSupported()) {
         ALOGE("Unable to initialize GNSS HAL.");
         return JNI_FALSE;
     }
 
-    // Set top level IGnss.hal callback.
-    if (gnssHal != nullptr) {
-        Return<bool> result = false;
-        sp<IGnssCallback_V2_1> gnssCbIface = new GnssCallback();
-        if (gnssHal_V2_1 != nullptr) {
-            result = gnssHal_V2_1->setCallback_2_1(gnssCbIface);
-        } else if (gnssHal_V2_0 != nullptr) {
-            result = gnssHal_V2_0->setCallback_2_0(gnssCbIface);
-        } else if (gnssHal_V1_1 != nullptr) {
-            result = gnssHal_V1_1->setCallback_1_1(gnssCbIface);
-        } else {
-            result = gnssHal->setCallback(gnssCbIface);
-        }
-        if (!checkHidlReturn(result, "IGnss setCallback() failed.")) {
-            return JNI_FALSE;
-        }
-    }
+    // Set top level IGnss HAL callback.
+    gnssHal->setCallback();
 
-    if (gnssHalAidl != nullptr) {
-        sp<IGnssCallbackAidl> gnssCbIfaceAidl = new GnssCallbackAidl();
-        auto status = gnssHalAidl->setCallback(gnssCbIfaceAidl);
-        if (!checkAidlStatus(status, "IGnssAidl setCallback() failed.")) {
-            return JNI_FALSE;
-        }
-    }
-
-    // Set IGnssPsds or IGnssXtra callback.
-    if (gnssPsdsAidlIface != nullptr) {
-        sp<IGnssPsdsCallbackAidl> gnssPsdsCallbackAidl = new GnssPsdsCallbackAidl();
-        auto status = gnssPsdsAidlIface->setCallback(gnssPsdsCallbackAidl);
-        if (!checkAidlStatus(status, "IGnssPsdsAidl setCallback() failed.")) {
-            gnssPsdsAidlIface = nullptr;
-        }
-    } else if (gnssXtraIface != nullptr) {
-        sp<IGnssXtraCallback> gnssXtraCbIface = new GnssXtraCallback();
-        auto result = gnssXtraIface->setCallback(gnssXtraCbIface);
-        if (!checkHidlReturn(result, "IGnssXtra setCallback() failed.")) {
-            gnssXtraIface = nullptr;
-        } else {
-            ALOGI("Unable to initialize IGnssXtra interface.");
-        }
+    // Set IGnssPsds callback.
+    if (gnssPsdsIface == nullptr ||
+        !gnssPsdsIface->setCallback(std::make_unique<gnss::GnssPsdsCallback>())) {
+        ALOGI("Unable to initialize IGnssPsds interface.");
     }
 
     // Set IAGnss callback.
@@ -1373,145 +420,47 @@
 }
 
 static void android_location_gnss_hal_GnssNative_cleanup(JNIEnv* /* env */, jclass) {
-    if (gnssHalAidl != nullptr) {
-        auto status = gnssHalAidl->close();
-        checkAidlStatus(status, "IGnssAidl close() failed.");
-    }
-
-    if (gnssHal != nullptr) {
-        auto result = gnssHal->cleanup();
-        checkHidlReturn(result, "IGnss cleanup() failed.");
-    }
+    gnssHal->close();
 }
 
 static jboolean android_location_gnss_hal_GnssNative_set_position_mode(
         JNIEnv* /* env */, jclass, jint mode, jint recurrence, jint min_interval,
         jint preferred_accuracy, jint preferred_time, jboolean low_power_mode) {
-    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
-        IGnssAidl::PositionModeOptions options;
-        options.mode = static_cast<IGnssAidl::GnssPositionMode>(mode);
-        options.recurrence = static_cast<IGnssAidl::GnssPositionRecurrence>(recurrence);
-        options.minIntervalMs = min_interval;
-        options.preferredAccuracyMeters = preferred_accuracy;
-        options.preferredTimeMs = preferred_time;
-        options.lowPowerMode = low_power_mode;
-        auto status = gnssHalAidl->setPositionMode(options);
-        return checkAidlStatus(status, "IGnssAidl setPositionMode() failed.");
-    }
-
-    Return<bool> result = false;
-    if (gnssHal_V1_1 != nullptr) {
-         result = gnssHal_V1_1->setPositionMode_1_1(static_cast<IGnss_V1_0::GnssPositionMode>(mode),
-                 static_cast<IGnss_V1_0::GnssPositionRecurrence>(recurrence),
-                 min_interval,
-                 preferred_accuracy,
-                 preferred_time,
-                 low_power_mode);
-     } else if (gnssHal != nullptr) {
-         result = gnssHal->setPositionMode(static_cast<IGnss_V1_0::GnssPositionMode>(mode),
-                 static_cast<IGnss_V1_0::GnssPositionRecurrence>(recurrence),
-                 min_interval,
-                 preferred_accuracy,
-                 preferred_time);
-    }
-
-    return checkHidlReturn(result, "IGnss setPositionMode() failed.");
+    return gnssHal->setPositionMode(mode, recurrence, min_interval, preferred_accuracy,
+                                    preferred_time, low_power_mode);
 }
 
 static jboolean android_location_gnss_hal_GnssNative_start(JNIEnv* /* env */, jclass) {
-    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
-        auto status = gnssHalAidl->start();
-        return checkAidlStatus(status, "IGnssAidl start() failed.");
-    }
-
-    if (gnssHal == nullptr) {
-        return JNI_FALSE;
-    }
-
-    auto result = gnssHal->start();
-    return checkHidlReturn(result, "IGnss start() failed.");
+    return gnssHal->start();
 }
 
 static jboolean android_location_gnss_hal_GnssNative_stop(JNIEnv* /* env */, jclass) {
-    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
-        auto status = gnssHalAidl->stop();
-        return checkAidlStatus(status, "IGnssAidl stop() failed.");
-    }
-
-    if (gnssHal == nullptr) {
-        return JNI_FALSE;
-    }
-
-    auto result = gnssHal->stop();
-    return checkHidlReturn(result, "IGnss stop() failed.");
+    return gnssHal->stop();
 }
 
 static jboolean android_location_gnss_hal_GnssNative_start_sv_status_collection(JNIEnv* /* env */,
                                                                                 jclass) {
-    isSvStatusRegistered = true;
-    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
-        auto status = gnssHalAidl->startSvStatus();
-        return checkAidlStatus(status, "IGnssAidl startSvStatus() failed.");
-    }
-    if (gnssHal == nullptr) {
-        return JNI_FALSE;
-    }
-    return JNI_TRUE;
+    return gnssHal->startSvStatus();
 }
 
 static jboolean android_location_gnss_hal_GnssNative_stop_sv_status_collection(JNIEnv* /* env */,
                                                                                jclass) {
-    isSvStatusRegistered = false;
-    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
-        auto status = gnssHalAidl->stopSvStatus();
-        return checkAidlStatus(status, "IGnssAidl stopSvStatus() failed.");
-    }
-    if (gnssHal == nullptr) {
-        return JNI_FALSE;
-    }
-    return JNI_TRUE;
+    return gnssHal->stopSvStatus();
 }
 
 static jboolean android_location_gnss_hal_GnssNative_start_nmea_message_collection(
         JNIEnv* /* env */, jclass) {
-    isNmeaRegistered = true;
-    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
-        auto status = gnssHalAidl->startNmea();
-        return checkAidlStatus(status, "IGnssAidl startNmea() failed.");
-    }
-    if (gnssHal == nullptr) {
-        return JNI_FALSE;
-    }
-    return JNI_TRUE;
+    return gnssHal->startNmea();
 }
 
 static jboolean android_location_gnss_hal_GnssNative_stop_nmea_message_collection(JNIEnv* /* env */,
                                                                                   jclass) {
-    isNmeaRegistered = false;
-    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
-        auto status = gnssHalAidl->stopNmea();
-        return checkAidlStatus(status, "IGnssAidl stopNmea() failed.");
-    }
-    if (gnssHal == nullptr) {
-        return JNI_FALSE;
-    }
-    return JNI_TRUE;
+    return gnssHal->stopNmea();
 }
 
 static void android_location_gnss_hal_GnssNative_delete_aiding_data(JNIEnv* /* env */, jclass,
                                                                     jint flags) {
-    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
-        auto status = gnssHalAidl->deleteAidingData(static_cast<IGnssAidl::GnssAidingData>(flags));
-        checkAidlStatus(status, "IGnssAidl deleteAidingData() failed.");
-        return;
-    }
-
-    if (gnssHal == nullptr) {
-        return;
-    }
-
-    auto result = gnssHal->deleteAidingData(static_cast<IGnss_V1_0::GnssAidingData>(flags));
-    checkHidlReturn(result, "IGnss deleteAidingData() failed.");
+    gnssHal->deleteAidingData(flags);
 }
 
 static void android_location_gnss_hal_GnssNative_agps_set_reference_location_cellid(
@@ -1535,30 +484,13 @@
 
 static jint android_location_gnss_hal_GnssNative_read_nmea(JNIEnv* env, jclass,
                                                            jbyteArray nmeaArray, jint buffer_size) {
-    // this should only be called from within a call to reportNmea
-    jbyte* nmea = reinterpret_cast<jbyte *>(env->GetPrimitiveArrayCritical(nmeaArray, 0));
-    int length = GnssCallback::sNmeaStringLength;
-    if (length > buffer_size)
-        length = buffer_size;
-    memcpy(nmea, GnssCallback::sNmeaString, length);
-    env->ReleasePrimitiveArrayCritical(nmeaArray, nmea, JNI_ABORT);
-    return (jint) length;
+    return gnssHal->readNmea(nmeaArray, buffer_size);
 }
 
 static void android_location_gnss_hal_GnssNative_inject_time(JNIEnv* /* env */, jclass, jlong time,
                                                              jlong timeReference,
                                                              jint uncertainty) {
-    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
-        auto status = gnssHalAidl->injectTime(time, timeReference, uncertainty);
-        checkAidlStatus(status, "IGnssAidl injectTime() failed.");
-        return;
-    }
-
-    if (gnssHal == nullptr) {
-        return;
-    }
-    auto result = gnssHal->injectTime(time, timeReference, uncertainty);
-    checkHidlReturn(result, "IGnss injectTime() failed.");
+    gnssHal->injectTime(time, timeReference, uncertainty);
 }
 
 static void android_location_gnss_hal_GnssNative_inject_best_location(
@@ -1568,58 +500,12 @@
         jfloat speedAccuracyMetersPerSecond, jfloat bearingAccuracyDegrees, jlong timestamp,
         jint elapsedRealtimeFlags, jlong elapsedRealtimeNanos,
         jdouble elapsedRealtimeUncertaintyNanos) {
-    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
-        GnssLocationAidl location =
-                createGnssLocation(gnssLocationFlags, latitudeDegrees, longitudeDegrees,
-                                   altitudeMeters, speedMetersPerSec, bearingDegrees,
-                                   horizontalAccuracyMeters, verticalAccuracyMeters,
-                                   speedAccuracyMetersPerSecond, bearingAccuracyDegrees, timestamp,
-                                   elapsedRealtimeFlags, elapsedRealtimeNanos,
-                                   elapsedRealtimeUncertaintyNanos);
-        auto status = gnssHalAidl->injectBestLocation(location);
-        checkAidlStatus(status, "IGnssAidl injectBestLocation() failed.");
-        return;
-    }
-
-    if (gnssHal_V2_0 != nullptr) {
-        GnssLocation_V2_0 location = createGnssLocation_V2_0(
-                gnssLocationFlags,
-                latitudeDegrees,
-                longitudeDegrees,
-                altitudeMeters,
-                speedMetersPerSec,
-                bearingDegrees,
-                horizontalAccuracyMeters,
-                verticalAccuracyMeters,
-                speedAccuracyMetersPerSecond,
-                bearingAccuracyDegrees,
-                timestamp,
-                elapsedRealtimeFlags,
-                elapsedRealtimeNanos,
-                elapsedRealtimeUncertaintyNanos);
-        auto result = gnssHal_V2_0->injectBestLocation_2_0(location);
-        checkHidlReturn(result, "IGnss injectBestLocation_2_0() failed.");
-        return;
-    }
-
-    if (gnssHal_V1_1 != nullptr) {
-        GnssLocation_V1_0 location = createGnssLocation_V1_0(
-                gnssLocationFlags,
-                latitudeDegrees,
-                longitudeDegrees,
-                altitudeMeters,
-                speedMetersPerSec,
-                bearingDegrees,
-                horizontalAccuracyMeters,
-                verticalAccuracyMeters,
-                speedAccuracyMetersPerSecond,
-                bearingAccuracyDegrees,
-                timestamp);
-        auto result = gnssHal_V1_1->injectBestLocation(location);
-        checkHidlReturn(result, "IGnss injectBestLocation() failed.");
-    }
-
-    ALOGE("IGnss injectBestLocation() is called but gnssHal_V1_1 is not available.");
+    gnssHal->injectBestLocation(gnssLocationFlags, latitudeDegrees, longitudeDegrees,
+                                altitudeMeters, speedMetersPerSec, bearingDegrees,
+                                horizontalAccuracyMeters, verticalAccuracyMeters,
+                                speedAccuracyMetersPerSecond, bearingAccuracyDegrees, timestamp,
+                                elapsedRealtimeFlags, elapsedRealtimeNanos,
+                                elapsedRealtimeUncertaintyNanos);
 }
 
 static void android_location_gnss_hal_GnssNative_inject_location(
@@ -1629,51 +515,25 @@
         jfloat speedAccuracyMetersPerSecond, jfloat bearingAccuracyDegrees, jlong timestamp,
         jint elapsedRealtimeFlags, jlong elapsedRealtimeNanos,
         jdouble elapsedRealtimeUncertaintyNanos) {
-    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
-        GnssLocationAidl location =
-                createGnssLocation(gnssLocationFlags, latitudeDegrees, longitudeDegrees,
-                                   altitudeMeters, speedMetersPerSec, bearingDegrees,
-                                   horizontalAccuracyMeters, verticalAccuracyMeters,
-                                   speedAccuracyMetersPerSecond, bearingAccuracyDegrees, timestamp,
-                                   elapsedRealtimeFlags, elapsedRealtimeNanos,
-                                   elapsedRealtimeUncertaintyNanos);
-        auto status = gnssHalAidl->injectLocation(location);
-        checkAidlStatus(status, "IGnssAidl injectLocation() failed.");
-        return;
-    }
-
-    if (gnssHal == nullptr) {
-        return;
-    }
-    auto result =
-            gnssHal->injectLocation(latitudeDegrees, longitudeDegrees, horizontalAccuracyMeters);
-    checkHidlReturn(result, "IGnss injectLocation() failed.");
+    gnssHal->injectLocation(gnssLocationFlags, latitudeDegrees, longitudeDegrees, altitudeMeters,
+                            speedMetersPerSec, bearingDegrees, horizontalAccuracyMeters,
+                            verticalAccuracyMeters, speedAccuracyMetersPerSecond,
+                            bearingAccuracyDegrees, timestamp, elapsedRealtimeFlags,
+                            elapsedRealtimeNanos, elapsedRealtimeUncertaintyNanos);
 }
 
 static jboolean android_location_gnss_hal_GnssNative_supports_psds(JNIEnv* /* env */, jclass) {
-    return (gnssPsdsAidlIface != nullptr || gnssXtraIface != nullptr) ? JNI_TRUE : JNI_FALSE;
+    return (gnssPsdsIface != nullptr) ? JNI_TRUE : JNI_FALSE;
 }
 
 static void android_location_gnss_hal_GnssNative_inject_psds_data(JNIEnv* env, jclass,
                                                                   jbyteArray data, jint length,
                                                                   jint psdsType) {
-    if (gnssPsdsAidlIface == nullptr && gnssXtraIface == nullptr) {
-        ALOGE("%s: IGnssPsdsAidl or IGnssXtra interface not available.", __func__);
+    if (gnssPsdsIface == nullptr) {
+        ALOGE("%s: IGnssPsds or IGnssXtra interface not available.", __func__);
         return;
     }
-
-    jbyte* bytes = reinterpret_cast<jbyte *>(env->GetPrimitiveArrayCritical(data, 0));
-    if (gnssPsdsAidlIface != nullptr) {
-        auto status = gnssPsdsAidlIface->injectPsdsData(static_cast<PsdsType>(psdsType),
-                                                        std::vector<uint8_t>((const uint8_t*)bytes,
-                                                                             (const uint8_t*)bytes +
-                                                                                     length));
-        checkAidlStatus(status, "IGnssPsdsAidl injectPsdsData() failed.");
-    } else if (gnssXtraIface != nullptr) {
-        auto result = gnssXtraIface->injectXtraData(std::string((const char*)bytes, length));
-        checkHidlReturn(result, "IGnssXtra injectXtraData() failed.");
-    }
-    env->ReleasePrimitiveArrayCritical(data, bytes, JNI_ABORT);
+    gnssPsdsIface->injectPsdsData(data, length, psdsType);
 }
 
 static void android_location_GnssNetworkConnectivityHandler_agps_data_conn_open(
diff --git a/services/core/jni/gnss/Android.bp b/services/core/jni/gnss/Android.bp
index e52df15..0531ae2 100644
--- a/services/core/jni/gnss/Android.bp
+++ b/services/core/jni/gnss/Android.bp
@@ -28,6 +28,8 @@
         "AGnssRil.cpp",
         "AGnssRilCallback.cpp",
         "GnssAntennaInfo.cpp",
+        "Gnss.cpp",
+        "GnssCallback.cpp",
         "GnssAntennaInfoCallback.cpp",
         "GnssBatching.cpp",
         "GnssBatchingCallback.cpp",
@@ -39,6 +41,8 @@
         "GnssMeasurementCallback.cpp",
         "GnssNavigationMessage.cpp",
         "GnssNavigationMessageCallback.cpp",
+        "GnssPsds.cpp",
+        "GnssPsdsCallback.cpp",
         "GnssVisibilityControl.cpp",
         "GnssVisibilityControlCallback.cpp",
         "MeasurementCorrections.cpp",
@@ -55,6 +59,7 @@
         "libhidlbase",
         "liblog",
         "libnativehelper",
+        "libhardware_legacy",
         "libutils",
         "android.hardware.gnss-V2-cpp",
         "android.hardware.gnss@1.0",
diff --git a/services/core/jni/gnss/Gnss.cpp b/services/core/jni/gnss/Gnss.cpp
new file mode 100644
index 0000000..f6459ea
--- /dev/null
+++ b/services/core/jni/gnss/Gnss.cpp
@@ -0,0 +1,759 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Define LOG_TAG before <log/log.h> to overwrite the default value.
+#define LOG_TAG "GnssJni"
+
+#include "Gnss.h"
+
+#include <binder/IServiceManager.h>
+
+#include "Utils.h"
+
+namespace android::gnss {
+
+using hardware::Return;
+
+using GnssLocationAidl = hardware::gnss::GnssLocation;
+using GnssLocation_V1_0 = hardware::gnss::V1_0::GnssLocation;
+using GnssLocation_V2_0 = hardware::gnss::V2_0::GnssLocation;
+using IAGnssAidl = hardware::gnss::IAGnss;
+using IAGnssRilAidl = hardware::gnss::IAGnssRil;
+using IGnssAidl = hardware::gnss::IGnss;
+using IGnss_V1_0 = hardware::gnss::V1_0::IGnss;
+using IGnss_V1_1 = hardware::gnss::V1_1::IGnss;
+using IGnss_V2_0 = hardware::gnss::V2_0::IGnss;
+using IGnss_V2_1 = hardware::gnss::V2_1::IGnss;
+using IGnssAntennaInfoAidl = hardware::gnss::IGnssAntennaInfo;
+using IGnssCallbackAidl = hardware::gnss::IGnssCallback;
+using IGnssCallback_V1_0 = hardware::gnss::V1_0::IGnssCallback;
+using IGnssCallback_V2_0 = hardware::gnss::V2_0::IGnssCallback;
+using IGnssCallback_V2_1 = hardware::gnss::V2_1::IGnssCallback;
+using IGnssConfigurationAidl = android::hardware::gnss::IGnssConfiguration;
+using IGnssDebugAidl = hardware::gnss::IGnssDebug;
+using android::hardware::gnss::IGnssPsds;
+
+namespace {
+
+GnssLocationAidl createGnssLocation(jint gnssLocationFlags, jdouble latitudeDegrees,
+                                    jdouble longitudeDegrees, jdouble altitudeMeters,
+                                    jfloat speedMetersPerSec, jfloat bearingDegrees,
+                                    jfloat horizontalAccuracyMeters, jfloat verticalAccuracyMeters,
+                                    jfloat speedAccuracyMetersPerSecond,
+                                    jfloat bearingAccuracyDegrees, jlong timestamp,
+                                    jint elapsedRealtimeFlags, jlong elapsedRealtimeNanos,
+                                    jdouble elapsedRealtimeUncertaintyNanos) {
+    GnssLocationAidl location;
+    location.gnssLocationFlags = static_cast<int>(gnssLocationFlags);
+    location.latitudeDegrees = static_cast<double>(latitudeDegrees);
+    location.longitudeDegrees = static_cast<double>(longitudeDegrees);
+    location.altitudeMeters = static_cast<double>(altitudeMeters);
+    location.speedMetersPerSec = static_cast<double>(speedMetersPerSec);
+    location.bearingDegrees = static_cast<double>(bearingDegrees);
+    location.horizontalAccuracyMeters = static_cast<double>(horizontalAccuracyMeters);
+    location.verticalAccuracyMeters = static_cast<double>(verticalAccuracyMeters);
+    location.speedAccuracyMetersPerSecond = static_cast<double>(speedAccuracyMetersPerSecond);
+    location.bearingAccuracyDegrees = static_cast<double>(bearingAccuracyDegrees);
+    location.timestampMillis = static_cast<uint64_t>(timestamp);
+
+    location.elapsedRealtime.flags = static_cast<int>(elapsedRealtimeFlags);
+    location.elapsedRealtime.timestampNs = static_cast<uint64_t>(elapsedRealtimeNanos);
+    location.elapsedRealtime.timeUncertaintyNs =
+            static_cast<double>(elapsedRealtimeUncertaintyNanos);
+
+    return location;
+}
+
+GnssLocation_V1_0 createGnssLocation_V1_0(jint gnssLocationFlags, jdouble latitudeDegrees,
+                                          jdouble longitudeDegrees, jdouble altitudeMeters,
+                                          jfloat speedMetersPerSec, jfloat bearingDegrees,
+                                          jfloat horizontalAccuracyMeters,
+                                          jfloat verticalAccuracyMeters,
+                                          jfloat speedAccuracyMetersPerSecond,
+                                          jfloat bearingAccuracyDegrees, jlong timestamp) {
+    GnssLocation_V1_0 location;
+    location.gnssLocationFlags = static_cast<uint16_t>(gnssLocationFlags);
+    location.latitudeDegrees = static_cast<double>(latitudeDegrees);
+    location.longitudeDegrees = static_cast<double>(longitudeDegrees);
+    location.altitudeMeters = static_cast<double>(altitudeMeters);
+    location.speedMetersPerSec = static_cast<float>(speedMetersPerSec);
+    location.bearingDegrees = static_cast<float>(bearingDegrees);
+    location.horizontalAccuracyMeters = static_cast<float>(horizontalAccuracyMeters);
+    location.verticalAccuracyMeters = static_cast<float>(verticalAccuracyMeters);
+    location.speedAccuracyMetersPerSecond = static_cast<float>(speedAccuracyMetersPerSecond);
+    location.bearingAccuracyDegrees = static_cast<float>(bearingAccuracyDegrees);
+    location.timestamp = static_cast<uint64_t>(timestamp);
+
+    return location;
+}
+
+GnssLocation_V2_0 createGnssLocation_V2_0(jint gnssLocationFlags, jdouble latitudeDegrees,
+                                          jdouble longitudeDegrees, jdouble altitudeMeters,
+                                          jfloat speedMetersPerSec, jfloat bearingDegrees,
+                                          jfloat horizontalAccuracyMeters,
+                                          jfloat verticalAccuracyMeters,
+                                          jfloat speedAccuracyMetersPerSecond,
+                                          jfloat bearingAccuracyDegrees, jlong timestamp,
+                                          jint elapsedRealtimeFlags, jlong elapsedRealtimeNanos,
+                                          jdouble elapsedRealtimeUncertaintyNanos) {
+    GnssLocation_V2_0 location;
+    location.v1_0 = createGnssLocation_V1_0(gnssLocationFlags, latitudeDegrees, longitudeDegrees,
+                                            altitudeMeters, speedMetersPerSec, bearingDegrees,
+                                            horizontalAccuracyMeters, verticalAccuracyMeters,
+                                            speedAccuracyMetersPerSecond, bearingAccuracyDegrees,
+                                            timestamp);
+
+    location.elapsedRealtime.flags = static_cast<uint16_t>(elapsedRealtimeFlags);
+    location.elapsedRealtime.timestampNs = static_cast<uint64_t>(elapsedRealtimeNanos);
+    location.elapsedRealtime.timeUncertaintyNs =
+            static_cast<uint64_t>(elapsedRealtimeUncertaintyNanos);
+
+    return location;
+}
+
+} // anonymous namespace
+
+// Implementation of GnssHal, which unifies all versions of GNSS HALs
+
+GnssHal::GnssHal() {
+    gnssHalAidl = waitForVintfService<IGnssAidl>();
+    if (gnssHalAidl != nullptr) {
+        ALOGD("Successfully got GNSS AIDL handle. Version=%d.", gnssHalAidl->getInterfaceVersion());
+        if (gnssHalAidl->getInterfaceVersion() >= 2) {
+            return;
+        }
+    }
+
+    ALOGD("Trying IGnss_V2_1::getService()");
+    gnssHal_V2_1 = IGnss_V2_1::getService();
+    if (gnssHal_V2_1 != nullptr) {
+        gnssHal_V2_0 = gnssHal_V2_1;
+        gnssHal_V1_1 = gnssHal_V2_1;
+        gnssHal = gnssHal_V2_1;
+        return;
+    }
+
+    ALOGD("gnssHal 2.1 was null, trying 2.0");
+    gnssHal_V2_0 = IGnss_V2_0::getService();
+    if (gnssHal_V2_0 != nullptr) {
+        gnssHal_V1_1 = gnssHal_V2_0;
+        gnssHal = gnssHal_V2_0;
+        return;
+    }
+
+    ALOGD("gnssHal 2.0 was null, trying 1.1");
+    gnssHal_V1_1 = IGnss_V1_1::getService();
+    if (gnssHal_V1_1 != nullptr) {
+        gnssHal = gnssHal_V1_1;
+        return;
+    }
+
+    ALOGD("gnssHal 1.1 was null, trying 1.0");
+    gnssHal = IGnss_V1_0::getService();
+}
+
+jboolean GnssHal::isSupported() {
+    return (gnssHalAidl != nullptr || gnssHal != nullptr) ? JNI_TRUE : JNI_FALSE;
+}
+
+void GnssHal::linkToDeath() {
+    // TODO: linkToDeath for AIDL HAL
+
+    if (gnssHal != nullptr) {
+        gnssHalDeathRecipient = new GnssDeathRecipient();
+        hardware::Return<bool> linked = gnssHal->linkToDeath(gnssHalDeathRecipient, /*cookie*/ 0);
+        if (!linked.isOk()) {
+            ALOGE("Transaction error in linking to GnssHAL death: %s",
+                  linked.description().c_str());
+        } else if (!linked) {
+            ALOGW("Unable to link to GnssHal death notifications");
+        } else {
+            ALOGD("Link to death notification successful");
+        }
+    }
+}
+
+jboolean GnssHal::setCallback() {
+    if (gnssHalAidl != nullptr) {
+        sp<IGnssCallbackAidl> gnssCbIfaceAidl = new GnssCallbackAidl();
+        auto status = gnssHalAidl->setCallback(gnssCbIfaceAidl);
+        if (!checkAidlStatus(status, "IGnssAidl setCallback() failed.")) {
+            return JNI_FALSE;
+        }
+    }
+    if (gnssHal != nullptr) {
+        Return<bool> result = false;
+        sp<IGnssCallback_V2_1> gnssCbIface = new GnssCallbackHidl();
+        if (gnssHal_V2_1 != nullptr) {
+            result = gnssHal_V2_1->setCallback_2_1(gnssCbIface);
+        } else if (gnssHal_V2_0 != nullptr) {
+            result = gnssHal_V2_0->setCallback_2_0(gnssCbIface);
+        } else if (gnssHal_V1_1 != nullptr) {
+            result = gnssHal_V1_1->setCallback_1_1(gnssCbIface);
+        } else {
+            result = gnssHal->setCallback(gnssCbIface);
+        }
+        if (!checkHidlReturn(result, "IGnss setCallback() failed.")) {
+            return JNI_FALSE;
+        }
+    }
+    return JNI_TRUE;
+}
+
+void GnssHal::close() {
+    if (gnssHalAidl != nullptr) {
+        auto status = gnssHalAidl->close();
+        checkAidlStatus(status, "IGnssAidl close() failed.");
+    }
+
+    if (gnssHal != nullptr) {
+        auto result = gnssHal->cleanup();
+        checkHidlReturn(result, "IGnss cleanup() failed.");
+    }
+}
+
+jboolean GnssHal::start() {
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        auto status = gnssHalAidl->start();
+        return checkAidlStatus(status, "IGnssAidl start() failed.");
+    }
+
+    if (gnssHal == nullptr) {
+        return JNI_FALSE;
+    }
+
+    auto result = gnssHal->start();
+    return checkHidlReturn(result, "IGnss start() failed.");
+}
+
+jboolean GnssHal::stop() {
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        auto status = gnssHalAidl->stop();
+        return checkAidlStatus(status, "IGnssAidl stop() failed.");
+    }
+
+    if (gnssHal == nullptr) {
+        return JNI_FALSE;
+    }
+
+    auto result = gnssHal->stop();
+    return checkHidlReturn(result, "IGnss stop() failed.");
+}
+
+jboolean GnssHal::startSvStatus() {
+    isSvStatusRegistered = true;
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        auto status = gnssHalAidl->startSvStatus();
+        return checkAidlStatus(status, "IGnssAidl startSvStatus() failed.");
+    }
+    if (gnssHal == nullptr) {
+        return JNI_FALSE;
+    }
+    return JNI_TRUE;
+}
+
+jboolean GnssHal::stopSvStatus() {
+    isSvStatusRegistered = false;
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        auto status = gnssHalAidl->stopSvStatus();
+        return checkAidlStatus(status, "IGnssAidl stopSvStatus() failed.");
+    }
+    if (gnssHal == nullptr) {
+        return JNI_FALSE;
+    }
+    return JNI_TRUE;
+}
+
+jboolean GnssHal::startNmea() {
+    isNmeaRegistered = true;
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        auto status = gnssHalAidl->startNmea();
+        return checkAidlStatus(status, "IGnssAidl startNmea() failed.");
+    }
+    if (gnssHal == nullptr) {
+        return JNI_FALSE;
+    }
+    return JNI_TRUE;
+}
+
+jboolean GnssHal::stopNmea() {
+    isNmeaRegistered = false;
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        auto status = gnssHalAidl->stopNmea();
+        return checkAidlStatus(status, "IGnssAidl stopNmea() failed.");
+    }
+    if (gnssHal == nullptr) {
+        return JNI_FALSE;
+    }
+    return JNI_TRUE;
+}
+
+jint GnssHal::readNmea(jbyteArray& nmeaArray, jint& buffer_size) {
+    // this should only be called from within a call to reportNmea
+    JNIEnv* env = getJniEnv();
+    jbyte* nmea = reinterpret_cast<jbyte*>(env->GetPrimitiveArrayCritical(nmeaArray, 0));
+    int length = GnssCallbackHidl::sNmeaStringLength;
+    if (length > buffer_size) {
+        length = buffer_size;
+    }
+    memcpy(nmea, GnssCallbackHidl::sNmeaString, length);
+    env->ReleasePrimitiveArrayCritical(nmeaArray, nmea, JNI_ABORT);
+    return (jint)length;
+}
+
+jboolean GnssHal::setPositionMode(jint mode, jint recurrence, jint min_interval,
+                                  jint preferred_accuracy, jint preferred_time,
+                                  jboolean low_power_mode) {
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        IGnssAidl::PositionModeOptions options;
+        options.mode = static_cast<IGnssAidl::GnssPositionMode>(mode);
+        options.recurrence = static_cast<IGnssAidl::GnssPositionRecurrence>(recurrence);
+        options.minIntervalMs = min_interval;
+        options.preferredAccuracyMeters = preferred_accuracy;
+        options.preferredTimeMs = preferred_time;
+        options.lowPowerMode = low_power_mode;
+        auto status = gnssHalAidl->setPositionMode(options);
+        return checkAidlStatus(status, "IGnssAidl setPositionMode() failed.");
+    }
+
+    Return<bool> result = false;
+    if (gnssHal_V1_1 != nullptr) {
+        result = gnssHal_V1_1->setPositionMode_1_1(static_cast<IGnss_V1_0::GnssPositionMode>(mode),
+                                                   static_cast<IGnss_V1_0::GnssPositionRecurrence>(
+                                                           recurrence),
+                                                   min_interval, preferred_accuracy, preferred_time,
+                                                   low_power_mode);
+    } else if (gnssHal != nullptr) {
+        result = gnssHal->setPositionMode(static_cast<IGnss_V1_0::GnssPositionMode>(mode),
+                                          static_cast<IGnss_V1_0::GnssPositionRecurrence>(
+                                                  recurrence),
+                                          min_interval, preferred_accuracy, preferred_time);
+    }
+    return checkHidlReturn(result, "IGnss setPositionMode() failed.");
+}
+
+void GnssHal::deleteAidingData(jint flags) {
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        auto status = gnssHalAidl->deleteAidingData(static_cast<IGnssAidl::GnssAidingData>(flags));
+        checkAidlStatus(status, "IGnssAidl deleteAidingData() failed.");
+        return;
+    }
+
+    if (gnssHal == nullptr) {
+        return;
+    }
+
+    auto result = gnssHal->deleteAidingData(static_cast<IGnss_V1_0::GnssAidingData>(flags));
+    checkHidlReturn(result, "IGnss deleteAidingData() failed.");
+}
+
+void GnssHal::injectTime(jlong time, jlong timeReference, jint uncertainty) {
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        auto status = gnssHalAidl->injectTime(time, timeReference, uncertainty);
+        checkAidlStatus(status, "IGnssAidl injectTime() failed.");
+        return;
+    }
+
+    if (gnssHal == nullptr) {
+        return;
+    }
+    auto result = gnssHal->injectTime(time, timeReference, uncertainty);
+    checkHidlReturn(result, "IGnss injectTime() failed.");
+}
+
+void GnssHal::injectLocation(jint gnssLocationFlags, jdouble latitudeDegrees,
+                             jdouble longitudeDegrees, jdouble altitudeMeters,
+                             jfloat speedMetersPerSec, jfloat bearingDegrees,
+                             jfloat horizontalAccuracyMeters, jfloat verticalAccuracyMeters,
+                             jfloat speedAccuracyMetersPerSecond, jfloat bearingAccuracyDegrees,
+                             jlong timestamp, jint elapsedRealtimeFlags, jlong elapsedRealtimeNanos,
+                             jdouble elapsedRealtimeUncertaintyNanos) {
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        GnssLocationAidl location =
+                createGnssLocation(gnssLocationFlags, latitudeDegrees, longitudeDegrees,
+                                   altitudeMeters, speedMetersPerSec, bearingDegrees,
+                                   horizontalAccuracyMeters, verticalAccuracyMeters,
+                                   speedAccuracyMetersPerSecond, bearingAccuracyDegrees, timestamp,
+                                   elapsedRealtimeFlags, elapsedRealtimeNanos,
+                                   elapsedRealtimeUncertaintyNanos);
+        auto status = gnssHalAidl->injectLocation(location);
+        checkAidlStatus(status, "IGnssAidl injectLocation() failed.");
+        return;
+    }
+
+    if (gnssHal == nullptr) {
+        return;
+    }
+    auto result =
+            gnssHal->injectLocation(latitudeDegrees, longitudeDegrees, horizontalAccuracyMeters);
+    checkHidlReturn(result, "IGnss injectLocation() failed.");
+}
+
+void GnssHal::injectBestLocation(jint gnssLocationFlags, jdouble latitudeDegrees,
+                                 jdouble longitudeDegrees, jdouble altitudeMeters,
+                                 jfloat speedMetersPerSec, jfloat bearingDegrees,
+                                 jfloat horizontalAccuracyMeters, jfloat verticalAccuracyMeters,
+                                 jfloat speedAccuracyMetersPerSecond, jfloat bearingAccuracyDegrees,
+                                 jlong timestamp, jint elapsedRealtimeFlags,
+                                 jlong elapsedRealtimeNanos,
+                                 jdouble elapsedRealtimeUncertaintyNanos) {
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        GnssLocationAidl location =
+                createGnssLocation(gnssLocationFlags, latitudeDegrees, longitudeDegrees,
+                                   altitudeMeters, speedMetersPerSec, bearingDegrees,
+                                   horizontalAccuracyMeters, verticalAccuracyMeters,
+                                   speedAccuracyMetersPerSecond, bearingAccuracyDegrees, timestamp,
+                                   elapsedRealtimeFlags, elapsedRealtimeNanos,
+                                   elapsedRealtimeUncertaintyNanos);
+        auto status = gnssHalAidl->injectBestLocation(location);
+        checkAidlStatus(status, "IGnssAidl injectBestLocation() failed.");
+        return;
+    }
+
+    if (gnssHal_V2_0 != nullptr) {
+        GnssLocation_V2_0 location =
+                createGnssLocation_V2_0(gnssLocationFlags, latitudeDegrees, longitudeDegrees,
+                                        altitudeMeters, speedMetersPerSec, bearingDegrees,
+                                        horizontalAccuracyMeters, verticalAccuracyMeters,
+                                        speedAccuracyMetersPerSecond, bearingAccuracyDegrees,
+                                        timestamp, elapsedRealtimeFlags, elapsedRealtimeNanos,
+                                        elapsedRealtimeUncertaintyNanos);
+        auto result = gnssHal_V2_0->injectBestLocation_2_0(location);
+        checkHidlReturn(result, "IGnss injectBestLocation_2_0() failed.");
+        return;
+    }
+
+    if (gnssHal_V1_1 != nullptr) {
+        GnssLocation_V1_0 location =
+                createGnssLocation_V1_0(gnssLocationFlags, latitudeDegrees, longitudeDegrees,
+                                        altitudeMeters, speedMetersPerSec, bearingDegrees,
+                                        horizontalAccuracyMeters, verticalAccuracyMeters,
+                                        speedAccuracyMetersPerSecond, bearingAccuracyDegrees,
+                                        timestamp);
+        auto result = gnssHal_V1_1->injectBestLocation(location);
+        checkHidlReturn(result, "IGnss injectBestLocation() failed.");
+        return;
+    }
+
+    ALOGE("IGnss injectBestLocation() is called but gnssHal_V1_1 is not available.");
+}
+
+std::unique_ptr<AGnssInterface> GnssHal::getAGnssInterface() {
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        sp<IAGnssAidl> agnssAidl;
+        auto status = gnssHalAidl->getExtensionAGnss(&agnssAidl);
+        if (checkAidlStatus(status, "Unable to get a handle to AGnss interface.")) {
+            return std::make_unique<gnss::AGnss>(agnssAidl);
+        }
+    } else if (gnssHal_V2_0 != nullptr) {
+        auto agnss_V2_0 = gnssHal_V2_0->getExtensionAGnss_2_0();
+        if (checkHidlReturn(agnss_V2_0, "Unable to get a handle to AGnss_V2_0")) {
+            return std::make_unique<gnss::AGnss_V2_0>(agnss_V2_0);
+        }
+    } else if (gnssHal != nullptr) {
+        auto agnss_V1_0 = gnssHal->getExtensionAGnss();
+        if (checkHidlReturn(agnss_V1_0, "Unable to get a handle to AGnss_V1_0")) {
+            return std::make_unique<gnss::AGnss_V1_0>(agnss_V1_0);
+        }
+    }
+    return nullptr;
+}
+
+std::unique_ptr<AGnssRilInterface> GnssHal::getAGnssRilInterface() {
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        sp<IAGnssRilAidl> agnssRilAidl;
+        auto status = gnssHalAidl->getExtensionAGnssRil(&agnssRilAidl);
+        if (checkAidlStatus(status, "Unable to get a handle to AGnssRil interface.")) {
+            return std::make_unique<gnss::AGnssRil>(agnssRilAidl);
+        }
+    } else if (gnssHal_V2_0 != nullptr) {
+        auto agnssRil_V2_0 = gnssHal_V2_0->getExtensionAGnssRil_2_0();
+        if (checkHidlReturn(agnssRil_V2_0, "Unable to get a handle to AGnssRil_V2_0")) {
+            return std::make_unique<gnss::AGnssRil_V2_0>(agnssRil_V2_0);
+        }
+    } else if (gnssHal != nullptr) {
+        auto agnssRil_V1_0 = gnssHal->getExtensionAGnssRil();
+        if (checkHidlReturn(agnssRil_V1_0, "Unable to get a handle to AGnssRil_V1_0")) {
+            return std::make_unique<gnss::AGnssRil_V1_0>(agnssRil_V1_0);
+        }
+    }
+    return nullptr;
+}
+
+std::unique_ptr<GnssNavigationMessageInterface> GnssHal::getGnssNavigationMessageInterface() {
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        sp<hardware::gnss::IGnssNavigationMessageInterface> gnssNavigationMessage;
+        auto status = gnssHalAidl->getExtensionGnssNavigationMessage(&gnssNavigationMessage);
+        if (checkAidlStatus(status,
+                            "Unable to get a handle to GnssNavigationMessage AIDL interface.")) {
+            return std::make_unique<gnss::GnssNavigationMessageAidl>(gnssNavigationMessage);
+        }
+    } else if (gnssHal != nullptr) {
+        auto gnssNavigationMessage = gnssHal->getExtensionGnssNavigationMessage();
+        if (checkHidlReturn(gnssNavigationMessage,
+                            "Unable to get a handle to GnssNavigationMessage interface.")) {
+            return std::make_unique<gnss::GnssNavigationMessageHidl>(gnssNavigationMessage);
+        }
+    }
+    return nullptr;
+}
+
+std::unique_ptr<GnssMeasurementInterface> GnssHal::getGnssMeasurementInterface() {
+    // Allow all causal combinations between IGnss.hal and IGnssMeasurement.hal. That means,
+    // 2.1@IGnss can be paired with {1.0, 1,1, 2.0, 2.1}@IGnssMeasurement
+    // 2.0@IGnss can be paired with {1.0, 1,1, 2.0}@IGnssMeasurement
+    // 1.1@IGnss can be paired {1.0, 1.1}@IGnssMeasurement
+    // 1.0@IGnss is paired with 1.0@IGnssMeasurement
+    if (gnssHalAidl != nullptr) {
+        sp<hardware::gnss::IGnssMeasurementInterface> gnssMeasurement;
+        auto status = gnssHalAidl->getExtensionGnssMeasurement(&gnssMeasurement);
+        if (checkAidlStatus(status, "Unable to get a handle to GnssMeasurement AIDL interface.")) {
+            return std::make_unique<android::gnss::GnssMeasurement>(gnssMeasurement);
+        }
+    }
+    if (gnssHal_V2_1 != nullptr) {
+        auto gnssMeasurement = gnssHal_V2_1->getExtensionGnssMeasurement_2_1();
+        if (checkHidlReturn(gnssMeasurement, "Unable to get a handle to GnssMeasurement_V2_1")) {
+            return std::make_unique<android::gnss::GnssMeasurement_V2_1>(gnssMeasurement);
+        }
+    }
+    if (gnssHal_V2_0 != nullptr) {
+        auto gnssMeasurement = gnssHal_V2_0->getExtensionGnssMeasurement_2_0();
+        if (checkHidlReturn(gnssMeasurement, "Unable to get a handle to GnssMeasurement_V2_0")) {
+            return std::make_unique<android::gnss::GnssMeasurement_V2_0>(gnssMeasurement);
+        }
+    }
+    if (gnssHal_V1_1 != nullptr) {
+        auto gnssMeasurement = gnssHal_V1_1->getExtensionGnssMeasurement_1_1();
+        if (checkHidlReturn(gnssMeasurement, "Unable to get a handle to GnssMeasurement_V1_1")) {
+            return std::make_unique<android::gnss::GnssMeasurement_V1_1>(gnssMeasurement);
+        }
+    }
+    if (gnssHal != nullptr) {
+        auto gnssMeasurement = gnssHal->getExtensionGnssMeasurement();
+        if (checkHidlReturn(gnssMeasurement, "Unable to get a handle to GnssMeasurement_V1_0")) {
+            return std::make_unique<android::gnss::GnssMeasurement_V1_0>(gnssMeasurement);
+        }
+    }
+    return nullptr;
+}
+
+std::unique_ptr<GnssDebugInterface> GnssHal::getGnssDebugInterface() {
+    // Allow all causal combinations between IGnss.hal and IGnssDebug.hal. That means,
+    // 2.0@IGnss can be paired with {1.0, 2.0}@IGnssDebug
+    // 1.0@IGnss is paired with 1.0@IGnssDebug
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        sp<IGnssDebugAidl> gnssDebugAidl;
+        auto status = gnssHalAidl->getExtensionGnssDebug(&gnssDebugAidl);
+        if (checkAidlStatus(status, "Unable to get a handle to GnssDebug interface.")) {
+            return std::make_unique<gnss::GnssDebug>(gnssDebugAidl);
+        }
+    }
+    if (gnssHal_V2_0 != nullptr) {
+        auto gnssDebug_V2_0 = gnssHal_V2_0->getExtensionGnssDebug_2_0();
+        if (checkHidlReturn(gnssDebug_V2_0, "Unable to get a handle to GnssDebug_V2_0.")) {
+            return std::make_unique<gnss::GnssDebug_V2_0>(gnssDebug_V2_0);
+        }
+    }
+    if (gnssHal != nullptr) {
+        auto gnssDebug_V1_0 = gnssHal->getExtensionGnssDebug();
+        if (checkHidlReturn(gnssDebug_V1_0, "Unable to get a handle to GnssDebug_V1_0.")) {
+            return std::make_unique<gnss::GnssDebug_V1_0>(gnssDebug_V1_0);
+        }
+    }
+    return nullptr;
+}
+
+std::unique_ptr<GnssConfigurationInterface> GnssHal::getGnssConfigurationInterface() {
+    if (gnssHalAidl != nullptr) {
+        sp<IGnssConfigurationAidl> gnssConfigurationAidl;
+        auto status = gnssHalAidl->getExtensionGnssConfiguration(&gnssConfigurationAidl);
+        if (checkAidlStatus(status,
+                            "Unable to get a handle to GnssConfiguration AIDL interface.")) {
+            return std::make_unique<android::gnss::GnssConfiguration>(gnssConfigurationAidl);
+        }
+    } else if (gnssHal_V2_1 != nullptr) {
+        auto gnssConfiguration = gnssHal_V2_1->getExtensionGnssConfiguration_2_1();
+        if (checkHidlReturn(gnssConfiguration,
+                            "Unable to get a handle to GnssConfiguration_V2_1")) {
+            return std::make_unique<android::gnss::GnssConfiguration_V2_1>(gnssConfiguration);
+        }
+    } else if (gnssHal_V2_0 != nullptr) {
+        auto gnssConfiguration = gnssHal_V2_0->getExtensionGnssConfiguration_2_0();
+        if (checkHidlReturn(gnssConfiguration,
+                            "Unable to get a handle to GnssConfiguration_V2_0")) {
+            return std::make_unique<android::gnss::GnssConfiguration_V2_0>(gnssConfiguration);
+        }
+    } else if (gnssHal_V1_1 != nullptr) {
+        auto gnssConfiguration = gnssHal_V1_1->getExtensionGnssConfiguration_1_1();
+        if (checkHidlReturn(gnssConfiguration,
+                            "Unable to get a handle to GnssConfiguration_V1_1")) {
+            return std::make_unique<gnss::GnssConfiguration_V1_1>(gnssConfiguration);
+        }
+    } else if (gnssHal != nullptr) {
+        auto gnssConfiguration = gnssHal->getExtensionGnssConfiguration();
+        if (checkHidlReturn(gnssConfiguration,
+                            "Unable to get a handle to GnssConfiguration_V1_0")) {
+            return std::make_unique<gnss::GnssConfiguration_V1_0>(gnssConfiguration);
+        }
+    }
+    return nullptr;
+}
+
+std::unique_ptr<GnssGeofenceInterface> GnssHal::getGnssGeofenceInterface() {
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        sp<hardware::gnss::IGnssGeofence> gnssGeofence;
+        auto status = gnssHalAidl->getExtensionGnssGeofence(&gnssGeofence);
+        if (checkAidlStatus(status, "Unable to get a handle to GnssGeofence AIDL interface.")) {
+            return std::make_unique<gnss::GnssGeofenceAidl>(gnssGeofence);
+        }
+    } else if (gnssHal != nullptr) {
+        auto gnssGeofencing = gnssHal->getExtensionGnssGeofencing();
+        if (checkHidlReturn(gnssGeofencing, "Unable to get a handle to GnssGeofencing")) {
+            return std::make_unique<gnss::GnssGeofenceHidl>(gnssGeofencing);
+        }
+    }
+    return nullptr;
+}
+
+std::unique_ptr<GnssBatchingInterface> GnssHal::getGnssBatchingInterface() {
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        sp<android::hardware::gnss::IGnssBatching> gnssBatchingAidl;
+        auto status = gnssHalAidl->getExtensionGnssBatching(&gnssBatchingAidl);
+        if (checkAidlStatus(status, "Unable to get a handle to GnssBatching interface.")) {
+            return std::make_unique<gnss::GnssBatching>(gnssBatchingAidl);
+        }
+    }
+    if (gnssHal_V2_0 != nullptr) {
+        auto gnssBatching_V2_0 = gnssHal_V2_0->getExtensionGnssBatching_2_0();
+        if (checkHidlReturn(gnssBatching_V2_0, "Unable to get a handle to GnssBatching_V2_0")) {
+            return std::make_unique<gnss::GnssBatching_V2_0>(gnssBatching_V2_0);
+        }
+    }
+    if (gnssHal != nullptr) {
+        auto gnssBatching_V1_0 = gnssHal->getExtensionGnssBatching();
+        if (checkHidlReturn(gnssBatching_V1_0, "Unable to get a handle to GnssBatching")) {
+            return std::make_unique<gnss::GnssBatching_V1_0>(gnssBatching_V1_0);
+        }
+    }
+    return nullptr;
+}
+
+std::unique_ptr<MeasurementCorrectionsInterface> GnssHal::getMeasurementCorrectionsInterface() {
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        sp<android::hardware::gnss::measurement_corrections::IMeasurementCorrectionsInterface>
+                gnssMeasurementCorrectionsAidl;
+        auto status =
+                gnssHalAidl->getExtensionMeasurementCorrections(&gnssMeasurementCorrectionsAidl);
+        if (checkAidlStatus(status,
+                            "Unable to get a handle to GnssVisibilityControl AIDL interface.")) {
+            return std::make_unique<gnss::MeasurementCorrectionsIface_Aidl>(
+                    gnssMeasurementCorrectionsAidl);
+        }
+    }
+    if (gnssHal_V2_1 != nullptr) {
+        auto gnssCorrections = gnssHal_V2_1->getExtensionMeasurementCorrections_1_1();
+        if (checkHidlReturn(gnssCorrections,
+                            "Unable to get a handle to GnssMeasurementCorrections HIDL "
+                            "interface")) {
+            return std::make_unique<gnss::MeasurementCorrectionsIface_V1_1>(gnssCorrections);
+        }
+    }
+    if (gnssHal_V2_0 != nullptr) {
+        auto gnssCorrections = gnssHal_V2_0->getExtensionMeasurementCorrections();
+        if (checkHidlReturn(gnssCorrections,
+                            "Unable to get a handle to GnssMeasurementCorrections HIDL "
+                            "interface")) {
+            return std::make_unique<gnss::MeasurementCorrectionsIface_V1_0>(gnssCorrections);
+        }
+    }
+    return nullptr;
+}
+
+std::unique_ptr<GnssVisibilityControlInterface> GnssHal::getGnssVisibilityControlInterface() {
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        sp<android::hardware::gnss::visibility_control::IGnssVisibilityControl>
+                gnssVisibilityControlAidl;
+        auto status = gnssHalAidl->getExtensionGnssVisibilityControl(&gnssVisibilityControlAidl);
+        if (checkAidlStatus(status,
+                            "Unable to get a handle to GnssVisibilityControl AIDL interface.")) {
+            return std::make_unique<gnss::GnssVisibilityControlAidl>(gnssVisibilityControlAidl);
+        }
+    } else if (gnssHal_V2_0 != nullptr) {
+        auto gnssVisibilityControlHidl = gnssHal_V2_0->getExtensionVisibilityControl();
+        if (checkHidlReturn(gnssVisibilityControlHidl,
+                            "Unable to get a handle to GnssVisibilityControl HIDL interface")) {
+            return std::make_unique<gnss::GnssVisibilityControlHidl>(gnssVisibilityControlHidl);
+        }
+    }
+    return nullptr;
+}
+
+std::unique_ptr<GnssAntennaInfoInterface> GnssHal::getGnssAntennaInfoInterface() {
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        sp<IGnssAntennaInfoAidl> gnssAntennaInfoAidl;
+        auto status = gnssHalAidl->getExtensionGnssAntennaInfo(&gnssAntennaInfoAidl);
+        if (checkAidlStatus(status, "Unable to get a handle to GnssAntennaInfo interface.")) {
+            return std::make_unique<gnss::GnssAntennaInfoAidl>(gnssAntennaInfoAidl);
+        }
+    } else if (gnssHal_V2_1 != nullptr) {
+        auto gnssAntennaInfo_V2_1 = gnssHal_V2_1->getExtensionGnssAntennaInfo();
+        if (checkHidlReturn(gnssAntennaInfo_V2_1,
+                            "Unable to get a handle to GnssAntennaInfo_V2_1")) {
+            return std::make_unique<gnss::GnssAntennaInfo_V2_1>(gnssAntennaInfo_V2_1);
+        }
+    }
+    return nullptr;
+}
+
+std::unique_ptr<GnssPsdsInterface> GnssHal::getGnssPsdsInterface() {
+    if (gnssHalAidl != nullptr) {
+        sp<IGnssPsds> gnssPsdsAidl;
+        auto status = gnssHalAidl->getExtensionPsds(&gnssPsdsAidl);
+        if (checkAidlStatus(status, "Unable to get a handle to PSDS interface.")) {
+            return std::make_unique<gnss::GnssPsdsAidl>(gnssPsdsAidl);
+        }
+    } else if (gnssHal != nullptr) {
+        auto gnssXtra = gnssHal->getExtensionXtra();
+        if (checkHidlReturn(gnssXtra, "Unable to get a handle to XTRA interface.")) {
+            return std::make_unique<gnss::GnssPsdsHidl>(gnssXtra);
+        }
+    }
+    return nullptr;
+}
+
+sp<hardware::gnss::IGnssPowerIndication> GnssHal::getGnssPowerIndicationInterface() {
+    if (gnssHalAidl != nullptr) {
+        sp<hardware::gnss::IGnssPowerIndication> gnssPowerIndication;
+        auto status = gnssHalAidl->getExtensionGnssPowerIndication(&gnssPowerIndication);
+        if (checkAidlStatus(status, "Unable to get a handle to GnssPowerIndication")) {
+            return gnssPowerIndication;
+        }
+    }
+    return nullptr;
+}
+
+sp<hardware::gnss::V1_0::IGnssNi> GnssHal::getGnssNiInterface() {
+    if (gnssHal != nullptr) {
+        auto gnssNi = gnssHal->getExtensionGnssNi();
+        if (checkHidlReturn(gnssNi, "Unable to get a handle to GnssNi")) {
+            return gnssNi;
+        }
+    }
+    return nullptr;
+}
+
+} // namespace android::gnss
diff --git a/services/core/jni/gnss/Gnss.h b/services/core/jni/gnss/Gnss.h
new file mode 100644
index 0000000..c6743d6
--- /dev/null
+++ b/services/core/jni/gnss/Gnss.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_SERVER_GNSS_GNSS_H
+#define _ANDROID_SERVER_GNSS_GNSS_H
+
+#pragma once
+
+#ifndef LOG_TAG
+#error LOG_TAG must be defined before including this file.
+#endif
+
+#include <android/hardware/gnss/1.0/IGnss.h>
+#include <android/hardware/gnss/1.1/IGnss.h>
+#include <android/hardware/gnss/2.0/IGnss.h>
+#include <android/hardware/gnss/2.1/IGnss.h>
+#include <android/hardware/gnss/BnGnss.h>
+#include <log/log.h>
+
+#include "AGnss.h"
+#include "AGnssRil.h"
+#include "GnssAntennaInfo.h"
+#include "GnssBatching.h"
+#include "GnssCallback.h"
+#include "GnssConfiguration.h"
+#include "GnssDebug.h"
+#include "GnssGeofence.h"
+#include "GnssMeasurement.h"
+#include "GnssNavigationMessage.h"
+#include "GnssPsds.h"
+#include "GnssVisibilityControl.h"
+#include "MeasurementCorrections.h"
+#include "jni.h"
+
+namespace android::gnss {
+
+struct GnssDeathRecipient : virtual public hardware::hidl_death_recipient {
+    // hidl_death_recipient interface
+    virtual void serviceDied(uint64_t cookie, const wp<hidl::base::V1_0::IBase>& who) override {
+        ALOGE("IGNSS hidl service failed, trying to recover...");
+
+        JNIEnv* env = android::AndroidRuntime::getJNIEnv();
+        env->CallVoidMethod(android::mCallbacksObj, method_reportGnssServiceDied);
+    }
+};
+
+class GnssHal {
+public:
+    GnssHal();
+    ~GnssHal() {}
+
+    jboolean isSupported();
+    jboolean setCallback();
+    jboolean start();
+    jboolean stop();
+    jboolean setPositionMode(jint mode, jint recurrence, jint min_interval, jint preferred_accuracy,
+                             jint preferred_time, jboolean low_power_mode);
+    jboolean startSvStatus();
+    jboolean stopSvStatus();
+    jboolean startNmea();
+    jboolean stopNmea();
+    jint readNmea(jbyteArray& nmeaArray, jint& buffer_size);
+    void linkToDeath();
+    void close();
+    void deleteAidingData(jint flags);
+    void injectTime(jlong time, jlong timeReference, jint uncertainty);
+    void injectLocation(jint gnssLocationFlags, jdouble latitudeDegrees, jdouble longitudeDegrees,
+                        jdouble altitudeMeters, jfloat speedMetersPerSec, jfloat bearingDegrees,
+                        jfloat horizontalAccuracyMeters, jfloat verticalAccuracyMeters,
+                        jfloat speedAccuracyMetersPerSecond, jfloat bearingAccuracyDegrees,
+                        jlong timestamp, jint elapsedRealtimeFlags, jlong elapsedRealtimeNanos,
+                        jdouble elapsedRealtimeUncertaintyNanos);
+    void injectBestLocation(jint gnssLocationFlags, jdouble latitudeDegrees,
+                            jdouble longitudeDegrees, jdouble altitudeMeters,
+                            jfloat speedMetersPerSec, jfloat bearingDegrees,
+                            jfloat horizontalAccuracyMeters, jfloat verticalAccuracyMeters,
+                            jfloat speedAccuracyMetersPerSecond, jfloat bearingAccuracyDegrees,
+                            jlong timestamp, jint elapsedRealtimeFlags, jlong elapsedRealtimeNanos,
+                            jdouble elapsedRealtimeUncertaintyNanos);
+
+    std::unique_ptr<AGnssInterface> getAGnssInterface();
+    std::unique_ptr<AGnssRilInterface> getAGnssRilInterface();
+    std::unique_ptr<GnssNavigationMessageInterface> getGnssNavigationMessageInterface();
+    std::unique_ptr<GnssMeasurementInterface> getGnssMeasurementInterface();
+    std::unique_ptr<GnssDebugInterface> getGnssDebugInterface();
+    std::unique_ptr<GnssConfigurationInterface> getGnssConfigurationInterface();
+    std::unique_ptr<GnssGeofenceInterface> getGnssGeofenceInterface();
+    std::unique_ptr<GnssBatchingInterface> getGnssBatchingInterface();
+    std::unique_ptr<MeasurementCorrectionsInterface> getMeasurementCorrectionsInterface();
+    std::unique_ptr<GnssVisibilityControlInterface> getGnssVisibilityControlInterface();
+    std::unique_ptr<GnssAntennaInfoInterface> getGnssAntennaInfoInterface();
+    std::unique_ptr<GnssPsdsInterface> getGnssPsdsInterface();
+
+    sp<hardware::gnss::IGnssPowerIndication> getGnssPowerIndicationInterface();
+    sp<hardware::gnss::V1_0::IGnssNi> getGnssNiInterface();
+
+private:
+    sp<GnssDeathRecipient> gnssHalDeathRecipient = nullptr;
+    sp<hardware::gnss::V1_0::IGnss> gnssHal = nullptr;
+    sp<hardware::gnss::V1_1::IGnss> gnssHal_V1_1 = nullptr;
+    sp<hardware::gnss::V2_0::IGnss> gnssHal_V2_0 = nullptr;
+    sp<hardware::gnss::V2_1::IGnss> gnssHal_V2_1 = nullptr;
+    sp<hardware::gnss::IGnss> gnssHalAidl = nullptr;
+};
+
+} // namespace android::gnss
+
+#endif // _ANDROID_SERVER_GNSS_Gnss_H
diff --git a/services/core/jni/gnss/GnssCallback.cpp b/services/core/jni/gnss/GnssCallback.cpp
new file mode 100644
index 0000000..b931e91
--- /dev/null
+++ b/services/core/jni/gnss/GnssCallback.cpp
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "GnssCallbckJni"
+
+#include "GnssCallback.h"
+
+#include <hardware_legacy/power.h>
+
+#define WAKE_LOCK_NAME "GPS"
+
+namespace android::gnss {
+
+using android::hardware::gnss::V1_0::GnssLocationFlags;
+using binder::Status;
+using hardware::hidl_vec;
+using hardware::Return;
+using hardware::Void;
+
+using GnssLocationAidl = android::hardware::gnss::GnssLocation;
+using GnssLocation_V1_0 = android::hardware::gnss::V1_0::GnssLocation;
+using GnssLocation_V2_0 = android::hardware::gnss::V2_0::GnssLocation;
+using IGnssCallbackAidl = android::hardware::gnss::IGnssCallback;
+using IGnssCallback_V1_0 = android::hardware::gnss::V1_0::IGnssCallback;
+using IGnssCallback_V2_0 = android::hardware::gnss::V2_0::IGnssCallback;
+using IGnssCallback_V2_1 = android::hardware::gnss::V2_1::IGnssCallback;
+
+jmethodID method_reportGnssServiceDied;
+
+namespace {
+
+jmethodID method_reportLocation;
+jmethodID method_reportStatus;
+jmethodID method_reportSvStatus;
+jmethodID method_reportNmea;
+jmethodID method_setTopHalCapabilities;
+jmethodID method_setGnssYearOfHardware;
+jmethodID method_setGnssHardwareModelName;
+jmethodID method_requestLocation;
+jmethodID method_requestUtcTime;
+
+// Returns true if location has lat/long information.
+inline bool hasLatLong(const GnssLocationAidl& location) {
+    return (location.gnssLocationFlags & hardware::gnss::GnssLocation::HAS_LAT_LONG) != 0;
+}
+
+// Returns true if location has lat/long information.
+inline bool hasLatLong(const GnssLocation_V1_0& location) {
+    return (static_cast<uint32_t>(location.gnssLocationFlags) & GnssLocationFlags::HAS_LAT_LONG) !=
+            0;
+}
+
+// Returns true if location has lat/long information.
+inline bool hasLatLong(const GnssLocation_V2_0& location) {
+    return hasLatLong(location.v1_0);
+}
+
+inline jboolean boolToJbool(bool value) {
+    return value ? JNI_TRUE : JNI_FALSE;
+}
+
+// Must match the value from GnssMeasurement.java
+const uint32_t SVID_FLAGS_HAS_BASEBAND_CN0 = (1 << 4);
+
+} // anonymous namespace
+
+bool isSvStatusRegistered = false;
+bool isNmeaRegistered = false;
+
+void Gnss_class_init_once(JNIEnv* env, jclass& clazz) {
+    method_reportLocation =
+            env->GetMethodID(clazz, "reportLocation", "(ZLandroid/location/Location;)V");
+    method_reportStatus = env->GetMethodID(clazz, "reportStatus", "(I)V");
+    method_reportSvStatus = env->GetMethodID(clazz, "reportSvStatus", "(I[I[F[F[F[F[F)V");
+    method_reportNmea = env->GetMethodID(clazz, "reportNmea", "(J)V");
+
+    method_setTopHalCapabilities = env->GetMethodID(clazz, "setTopHalCapabilities", "(I)V");
+    method_setGnssYearOfHardware = env->GetMethodID(clazz, "setGnssYearOfHardware", "(I)V");
+    method_setGnssHardwareModelName =
+            env->GetMethodID(clazz, "setGnssHardwareModelName", "(Ljava/lang/String;)V");
+
+    method_requestLocation = env->GetMethodID(clazz, "requestLocation", "(ZZ)V");
+    method_requestUtcTime = env->GetMethodID(clazz, "requestUtcTime", "()V");
+    method_reportGnssServiceDied = env->GetMethodID(clazz, "reportGnssServiceDied", "()V");
+}
+
+Status GnssCallbackAidl::gnssSetCapabilitiesCb(const int capabilities) {
+    ALOGD("GnssCallbackAidl::%s: %du\n", __func__, capabilities);
+    JNIEnv* env = getJniEnv();
+    env->CallVoidMethod(mCallbacksObj, method_setTopHalCapabilities, capabilities);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return Status::ok();
+}
+
+Status GnssCallbackAidl::gnssStatusCb(const GnssStatusValue status) {
+    JNIEnv* env = getJniEnv();
+    env->CallVoidMethod(mCallbacksObj, method_reportStatus, status);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return Status::ok();
+}
+
+Status GnssCallbackAidl::gnssSvStatusCb(const std::vector<GnssSvInfo>& svInfoList) {
+    GnssCallbackHidl::gnssSvStatusCbImpl<std::vector<GnssSvInfo>, GnssSvInfo>(svInfoList);
+    return Status::ok();
+}
+
+Status GnssCallbackAidl::gnssLocationCb(const hardware::gnss::GnssLocation& location) {
+    GnssCallbackHidl::gnssLocationCbImpl<hardware::gnss::GnssLocation>(location);
+    return Status::ok();
+}
+
+Status GnssCallbackAidl::gnssNmeaCb(const int64_t timestamp, const std::string& nmea) {
+    // In AIDL v1, if no listener is registered, do not report nmea to the framework.
+    if (getInterfaceVersion() <= 1) {
+        if (!isNmeaRegistered) {
+            return Status::ok();
+        }
+    }
+    JNIEnv* env = getJniEnv();
+    /*
+     * The Java code will call back to read these values.
+     * We do this to avoid creating unnecessary String objects.
+     */
+    GnssCallbackHidl::sNmeaString = nmea.c_str();
+    GnssCallbackHidl::sNmeaStringLength = nmea.size();
+
+    env->CallVoidMethod(mCallbacksObj, method_reportNmea, timestamp);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return Status::ok();
+}
+
+Status GnssCallbackAidl::gnssAcquireWakelockCb() {
+    acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_NAME);
+    return Status::ok();
+}
+
+Status GnssCallbackAidl::gnssReleaseWakelockCb() {
+    release_wake_lock(WAKE_LOCK_NAME);
+    return Status::ok();
+}
+
+Status GnssCallbackAidl::gnssSetSystemInfoCb(const GnssSystemInfo& info) {
+    ALOGD("%s: yearOfHw=%d, name=%s\n", __func__, info.yearOfHw, info.name.c_str());
+    JNIEnv* env = getJniEnv();
+    env->CallVoidMethod(mCallbacksObj, method_setGnssYearOfHardware, info.yearOfHw);
+    jstring jstringName = env->NewStringUTF(info.name.c_str());
+    env->CallVoidMethod(mCallbacksObj, method_setGnssHardwareModelName, jstringName);
+    if (jstringName) {
+        env->DeleteLocalRef(jstringName);
+    }
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return Status::ok();
+}
+
+Status GnssCallbackAidl::gnssRequestTimeCb() {
+    JNIEnv* env = getJniEnv();
+    env->CallVoidMethod(mCallbacksObj, method_requestUtcTime);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return Status::ok();
+}
+
+Status GnssCallbackAidl::gnssRequestLocationCb(const bool independentFromGnss,
+                                               const bool isUserEmergency) {
+    JNIEnv* env = getJniEnv();
+    env->CallVoidMethod(mCallbacksObj, method_requestLocation, boolToJbool(independentFromGnss),
+                        boolToJbool(isUserEmergency));
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return Status::ok();
+}
+
+// Implementation of IGnssCallbackHidl
+
+Return<void> GnssCallbackHidl::gnssNameCb(const android::hardware::hidl_string& name) {
+    ALOGD("%s: name=%s\n", __func__, name.c_str());
+
+    JNIEnv* env = getJniEnv();
+    jstring jstringName = env->NewStringUTF(name.c_str());
+    env->CallVoidMethod(mCallbacksObj, method_setGnssHardwareModelName, jstringName);
+    if (jstringName) {
+        env->DeleteLocalRef(jstringName);
+    }
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+
+    return Void();
+}
+
+const char* GnssCallbackHidl::sNmeaString = nullptr;
+size_t GnssCallbackHidl::sNmeaStringLength = 0;
+
+template <class T>
+Return<void> GnssCallbackHidl::gnssLocationCbImpl(const T& location) {
+    JNIEnv* env = getJniEnv();
+
+    jobject jLocation = translateGnssLocation(env, location);
+
+    env->CallVoidMethod(mCallbacksObj, method_reportLocation, boolToJbool(hasLatLong(location)),
+                        jLocation);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    env->DeleteLocalRef(jLocation);
+    return Void();
+}
+
+Return<void> GnssCallbackHidl::gnssLocationCb(const GnssLocation_V1_0& location) {
+    return gnssLocationCbImpl<GnssLocation_V1_0>(location);
+}
+
+Return<void> GnssCallbackHidl::gnssLocationCb_2_0(const GnssLocation_V2_0& location) {
+    return gnssLocationCbImpl<GnssLocation_V2_0>(location);
+}
+
+Return<void> GnssCallbackHidl::gnssStatusCb(const IGnssCallback_V2_0::GnssStatusValue status) {
+    JNIEnv* env = getJniEnv();
+    env->CallVoidMethod(mCallbacksObj, method_reportStatus, status);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return Void();
+}
+
+template <>
+uint32_t GnssCallbackHidl::getHasBasebandCn0DbHzFlag(
+        const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svStatus) {
+    return SVID_FLAGS_HAS_BASEBAND_CN0;
+}
+
+template <>
+uint32_t GnssCallbackHidl::getHasBasebandCn0DbHzFlag(
+        const std::vector<IGnssCallbackAidl::GnssSvInfo>& svStatus) {
+    return SVID_FLAGS_HAS_BASEBAND_CN0;
+}
+
+template <>
+double GnssCallbackHidl::getBasebandCn0DbHz(
+        const std::vector<IGnssCallbackAidl::GnssSvInfo>& svInfoList, size_t i) {
+    return svInfoList[i].basebandCN0DbHz;
+}
+
+template <>
+double GnssCallbackHidl::getBasebandCn0DbHz(
+        const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList, size_t i) {
+    return svInfoList[i].basebandCN0DbHz;
+}
+
+template <>
+uint32_t GnssCallbackHidl::getGnssSvInfoListSize(const IGnssCallback_V1_0::GnssSvStatus& svStatus) {
+    return svStatus.numSvs;
+}
+
+template <>
+uint32_t GnssCallbackHidl::getConstellationType(const IGnssCallback_V1_0::GnssSvStatus& svStatus,
+                                                size_t i) {
+    return static_cast<uint32_t>(svStatus.gnssSvList.data()[i].constellation);
+}
+
+template <>
+uint32_t GnssCallbackHidl::getConstellationType(
+        const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList, size_t i) {
+    return static_cast<uint32_t>(svInfoList[i].v2_0.constellation);
+}
+
+template <class T_list, class T_sv_info>
+Return<void> GnssCallbackHidl::gnssSvStatusCbImpl(const T_list& svStatus) {
+    // In HIDL or AIDL v1, if no listener is registered, do not report svInfoList to the framework.
+    if (!isSvStatusRegistered) {
+        return Void();
+    }
+
+    JNIEnv* env = getJniEnv();
+
+    uint32_t listSize = getGnssSvInfoListSize(svStatus);
+
+    jintArray svidWithFlagArray = env->NewIntArray(listSize);
+    jfloatArray cn0Array = env->NewFloatArray(listSize);
+    jfloatArray elevArray = env->NewFloatArray(listSize);
+    jfloatArray azimArray = env->NewFloatArray(listSize);
+    jfloatArray carrierFreqArray = env->NewFloatArray(listSize);
+    jfloatArray basebandCn0Array = env->NewFloatArray(listSize);
+
+    jint* svidWithFlags = env->GetIntArrayElements(svidWithFlagArray, 0);
+    jfloat* cn0s = env->GetFloatArrayElements(cn0Array, 0);
+    jfloat* elev = env->GetFloatArrayElements(elevArray, 0);
+    jfloat* azim = env->GetFloatArrayElements(azimArray, 0);
+    jfloat* carrierFreq = env->GetFloatArrayElements(carrierFreqArray, 0);
+    jfloat* basebandCn0s = env->GetFloatArrayElements(basebandCn0Array, 0);
+
+    /*
+     * Read GNSS SV info.
+     */
+    for (size_t i = 0; i < listSize; ++i) {
+        enum ShiftWidth : uint8_t { SVID_SHIFT_WIDTH = 12, CONSTELLATION_TYPE_SHIFT_WIDTH = 8 };
+
+        const T_sv_info& info = getGnssSvInfoOfIndex(svStatus, i);
+        svidWithFlags[i] = (info.svid << SVID_SHIFT_WIDTH) |
+                (getConstellationType(svStatus, i) << CONSTELLATION_TYPE_SHIFT_WIDTH) |
+                static_cast<uint32_t>(info.svFlag);
+        cn0s[i] = info.cN0Dbhz;
+        elev[i] = info.elevationDegrees;
+        azim[i] = info.azimuthDegrees;
+        carrierFreq[i] = info.carrierFrequencyHz;
+        svidWithFlags[i] |= getHasBasebandCn0DbHzFlag(svStatus);
+        basebandCn0s[i] = getBasebandCn0DbHz(svStatus, i);
+    }
+
+    env->ReleaseIntArrayElements(svidWithFlagArray, svidWithFlags, 0);
+    env->ReleaseFloatArrayElements(cn0Array, cn0s, 0);
+    env->ReleaseFloatArrayElements(elevArray, elev, 0);
+    env->ReleaseFloatArrayElements(azimArray, azim, 0);
+    env->ReleaseFloatArrayElements(carrierFreqArray, carrierFreq, 0);
+    env->ReleaseFloatArrayElements(basebandCn0Array, basebandCn0s, 0);
+
+    env->CallVoidMethod(mCallbacksObj, method_reportSvStatus, static_cast<jint>(listSize),
+                        svidWithFlagArray, cn0Array, elevArray, azimArray, carrierFreqArray,
+                        basebandCn0Array);
+
+    env->DeleteLocalRef(svidWithFlagArray);
+    env->DeleteLocalRef(cn0Array);
+    env->DeleteLocalRef(elevArray);
+    env->DeleteLocalRef(azimArray);
+    env->DeleteLocalRef(carrierFreqArray);
+    env->DeleteLocalRef(basebandCn0Array);
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return Void();
+}
+
+Return<void> GnssCallbackHidl::gnssNmeaCb(int64_t timestamp,
+                                          const ::android::hardware::hidl_string& nmea) {
+    // In HIDL, if no listener is registered, do not report nmea to the framework.
+    if (!isNmeaRegistered) {
+        return Void();
+    }
+    JNIEnv* env = getJniEnv();
+    /*
+     * The Java code will call back to read these values.
+     * We do this to avoid creating unnecessary String objects.
+     */
+    sNmeaString = nmea.c_str();
+    sNmeaStringLength = nmea.size();
+
+    env->CallVoidMethod(mCallbacksObj, method_reportNmea, timestamp);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return Void();
+}
+
+Return<void> GnssCallbackHidl::gnssSetCapabilitesCb(uint32_t capabilities) {
+    ALOGD("%s: %du\n", __func__, capabilities);
+
+    JNIEnv* env = getJniEnv();
+    env->CallVoidMethod(mCallbacksObj, method_setTopHalCapabilities, capabilities);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return Void();
+}
+
+Return<void> GnssCallbackHidl::gnssSetCapabilitiesCb_2_0(uint32_t capabilities) {
+    return GnssCallbackHidl::gnssSetCapabilitesCb(capabilities);
+}
+
+Return<void> GnssCallbackHidl::gnssSetCapabilitiesCb_2_1(uint32_t capabilities) {
+    return GnssCallbackHidl::gnssSetCapabilitesCb(capabilities);
+}
+
+Return<void> GnssCallbackHidl::gnssAcquireWakelockCb() {
+    acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_NAME);
+    return Void();
+}
+
+Return<void> GnssCallbackHidl::gnssReleaseWakelockCb() {
+    release_wake_lock(WAKE_LOCK_NAME);
+    return Void();
+}
+
+Return<void> GnssCallbackHidl::gnssRequestTimeCb() {
+    JNIEnv* env = getJniEnv();
+    env->CallVoidMethod(mCallbacksObj, method_requestUtcTime);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return Void();
+}
+
+Return<void> GnssCallbackHidl::gnssRequestLocationCb(const bool independentFromGnss) {
+    return GnssCallbackHidl::gnssRequestLocationCb_2_0(independentFromGnss, /* isUserEmergency= */
+                                                       false);
+}
+
+Return<void> GnssCallbackHidl::gnssRequestLocationCb_2_0(const bool independentFromGnss,
+                                                         const bool isUserEmergency) {
+    JNIEnv* env = getJniEnv();
+    env->CallVoidMethod(mCallbacksObj, method_requestLocation, boolToJbool(independentFromGnss),
+                        boolToJbool(isUserEmergency));
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return Void();
+}
+
+Return<void> GnssCallbackHidl::gnssSetSystemInfoCb(const IGnssCallback_V2_0::GnssSystemInfo& info) {
+    ALOGD("%s: yearOfHw=%d\n", __func__, info.yearOfHw);
+
+    JNIEnv* env = getJniEnv();
+    env->CallVoidMethod(mCallbacksObj, method_setGnssYearOfHardware, info.yearOfHw);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return Void();
+}
+
+} // namespace android::gnss
\ No newline at end of file
diff --git a/services/core/jni/gnss/GnssCallback.h b/services/core/jni/gnss/GnssCallback.h
new file mode 100644
index 0000000..a7f96fb
--- /dev/null
+++ b/services/core/jni/gnss/GnssCallback.h
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_SERVER_GNSS_GNSSCALLBACK_H
+#define _ANDROID_SERVER_GNSS_GNSSCALLBACK_H
+
+#pragma once
+
+#ifndef LOG_TAG
+#error LOG_TAG must be defined before including this file.
+#endif
+
+#include <android/hardware/gnss/2.1/IGnss.h>
+#include <android/hardware/gnss/BnGnssCallback.h>
+#include <log/log.h>
+
+#include <vector>
+
+#include "Utils.h"
+#include "jni.h"
+
+namespace android::gnss {
+
+namespace {
+
+extern jmethodID method_reportLocation;
+extern jmethodID method_reportStatus;
+extern jmethodID method_reportSvStatus;
+extern jmethodID method_reportNmea;
+extern jmethodID method_setTopHalCapabilities;
+extern jmethodID method_setGnssYearOfHardware;
+extern jmethodID method_setGnssHardwareModelName;
+extern jmethodID method_requestLocation;
+extern jmethodID method_requestUtcTime;
+
+} // anonymous namespace
+
+extern bool isSvStatusRegistered;
+extern bool isNmeaRegistered;
+
+extern jmethodID method_reportGnssServiceDied;
+
+void Gnss_class_init_once(JNIEnv* env, jclass& clazz);
+
+/*
+ * GnssCallbackAidl class implements the callback methods for AIDL IGnssCallback interface.
+ */
+class GnssCallbackAidl : public hardware::gnss::BnGnssCallback {
+public:
+    binder::Status gnssSetCapabilitiesCb(const int capabilities) override;
+    binder::Status gnssStatusCb(const GnssStatusValue status) override;
+    binder::Status gnssSvStatusCb(const std::vector<GnssSvInfo>& svInfoList) override;
+    binder::Status gnssLocationCb(const hardware::gnss::GnssLocation& location) override;
+    binder::Status gnssNmeaCb(const int64_t timestamp, const std::string& nmea) override;
+    binder::Status gnssAcquireWakelockCb() override;
+    binder::Status gnssReleaseWakelockCb() override;
+    binder::Status gnssSetSystemInfoCb(const GnssSystemInfo& info) override;
+    binder::Status gnssRequestTimeCb() override;
+    binder::Status gnssRequestLocationCb(const bool independentFromGnss,
+                                         const bool isUserEmergency) override;
+};
+
+/*
+ * GnssCallbackHidl class implements the callback methods for HIDL IGnssCallback interface.
+ */
+struct GnssCallbackHidl : public hardware::gnss::V2_1::IGnssCallback {
+    hardware::Return<void> gnssLocationCb(
+            const hardware::gnss::V1_0::GnssLocation& location) override;
+    hardware::Return<void> gnssStatusCb(
+            const hardware::gnss::V1_0::IGnssCallback::GnssStatusValue status) override;
+    hardware::Return<void> gnssSvStatusCb(
+            const hardware::gnss::V1_0::IGnssCallback::GnssSvStatus& svStatus) override {
+        return gnssSvStatusCbImpl<hardware::gnss::V1_0::IGnssCallback::GnssSvStatus,
+                                  hardware::gnss::V1_0::IGnssCallback::GnssSvInfo>(svStatus);
+    }
+    hardware::Return<void> gnssNmeaCb(int64_t timestamp,
+                                      const hardware::hidl_string& nmea) override;
+    hardware::Return<void> gnssSetCapabilitesCb(uint32_t capabilities) override;
+    hardware::Return<void> gnssAcquireWakelockCb() override;
+    hardware::Return<void> gnssReleaseWakelockCb() override;
+    hardware::Return<void> gnssRequestTimeCb() override;
+    hardware::Return<void> gnssRequestLocationCb(const bool independentFromGnss) override;
+
+    hardware::Return<void> gnssSetSystemInfoCb(
+            const hardware::gnss::V1_0::IGnssCallback::GnssSystemInfo& info) override;
+
+    // New in 1.1
+    hardware::Return<void> gnssNameCb(const hardware::hidl_string& name) override;
+
+    // New in 2.0
+    hardware::Return<void> gnssRequestLocationCb_2_0(const bool independentFromGnss,
+                                                     const bool isUserEmergency) override;
+    hardware::Return<void> gnssSetCapabilitiesCb_2_0(uint32_t capabilities) override;
+    hardware::Return<void> gnssLocationCb_2_0(
+            const hardware::gnss::V2_0::GnssLocation& location) override;
+    hardware::Return<void> gnssSvStatusCb_2_0(
+            const hardware::hidl_vec<hardware::gnss::V2_0::IGnssCallback::GnssSvInfo>& svInfoList)
+            override {
+        return gnssSvStatusCbImpl<
+                hardware::hidl_vec<hardware::gnss::V2_0::IGnssCallback::GnssSvInfo>,
+                hardware::gnss::V1_0::IGnssCallback::GnssSvInfo>(svInfoList);
+    }
+
+    // New in 2.1
+    hardware::Return<void> gnssSvStatusCb_2_1(
+            const hardware::hidl_vec<hardware::gnss::V2_1::IGnssCallback::GnssSvInfo>& svInfoList)
+            override {
+        return gnssSvStatusCbImpl<
+                hardware::hidl_vec<hardware::gnss::V2_1::IGnssCallback::GnssSvInfo>,
+                hardware::gnss::V1_0::IGnssCallback::GnssSvInfo>(svInfoList);
+    }
+    hardware::Return<void> gnssSetCapabilitiesCb_2_1(uint32_t capabilities) override;
+
+    // TODO: Reconsider allocation cost vs threadsafety on these statics
+    static const char* sNmeaString;
+    static size_t sNmeaStringLength;
+
+    template <class T>
+    static hardware::Return<void> gnssLocationCbImpl(const T& location);
+
+    template <class T_list, class T_sv_info>
+    static hardware::Return<void> gnssSvStatusCbImpl(const T_list& svStatus);
+
+private:
+    template <class T>
+    static uint32_t getHasBasebandCn0DbHzFlag(const T& svStatus) {
+        return 0;
+    }
+
+    template <class T>
+    static double getBasebandCn0DbHz(const T& svStatus, size_t i) {
+        return 0.0;
+    }
+
+    template <class T>
+    static uint32_t getGnssSvInfoListSize(const T& svInfoList) {
+        return svInfoList.size();
+    }
+
+    static const hardware::gnss::IGnssCallback::GnssSvInfo& getGnssSvInfoOfIndex(
+            const std::vector<hardware::gnss::IGnssCallback::GnssSvInfo>& svInfoList, size_t i) {
+        return svInfoList[i];
+    }
+
+    static const hardware::gnss::V1_0::IGnssCallback::GnssSvInfo& getGnssSvInfoOfIndex(
+            const hardware::gnss::V1_0::IGnssCallback::GnssSvStatus& svStatus, size_t i) {
+        return svStatus.gnssSvList.data()[i];
+    }
+
+    static const hardware::gnss::V1_0::IGnssCallback::GnssSvInfo& getGnssSvInfoOfIndex(
+            const hardware::hidl_vec<hardware::gnss::V2_0::IGnssCallback::GnssSvInfo>& svInfoList,
+            size_t i) {
+        return svInfoList[i].v1_0;
+    }
+
+    static const hardware::gnss::V1_0::IGnssCallback::GnssSvInfo& getGnssSvInfoOfIndex(
+            const hardware::hidl_vec<hardware::gnss::V2_1::IGnssCallback::GnssSvInfo>& svInfoList,
+            size_t i) {
+        return svInfoList[i].v2_0.v1_0;
+    }
+
+    template <class T>
+    static uint32_t getConstellationType(const T& svInfoList, size_t i) {
+        return static_cast<uint32_t>(svInfoList[i].constellation);
+    }
+};
+
+} // namespace android::gnss
+
+#endif // _ANDROID_SERVER_GNSS_GNSSCALLBACK_H
\ No newline at end of file
diff --git a/services/core/jni/gnss/GnssDebug.cpp b/services/core/jni/gnss/GnssDebug.cpp
index da53317..263a6c6 100644
--- a/services/core/jni/gnss/GnssDebug.cpp
+++ b/services/core/jni/gnss/GnssDebug.cpp
@@ -104,4 +104,10 @@
     return satelliteDataArray[i];
 }
 
+template <>
+int64_t GnssDebugUtil::getTimeEstimateMs(
+        const android::hardware::gnss::IGnssDebug::DebugData& data) {
+    return data.time.timeEstimateMs;
+}
+
 } // namespace android::gnss
diff --git a/services/core/jni/gnss/GnssDebug.h b/services/core/jni/gnss/GnssDebug.h
index 1e1a7b4..e02c2ac 100644
--- a/services/core/jni/gnss/GnssDebug.h
+++ b/services/core/jni/gnss/GnssDebug.h
@@ -113,12 +113,6 @@
     return data.time.timeEstimate;
 }
 
-template <>
-int64_t GnssDebugUtil::getTimeEstimateMs(
-        const android::hardware::gnss::IGnssDebug::DebugData& data) {
-    return data.time.timeEstimateMs;
-}
-
 template <class T_DebugData, class T_SatelliteData>
 jstring GnssDebugUtil::parseDebugData(JNIEnv* env, std::stringstream& internalState,
                                       const T_DebugData& data) {
diff --git a/services/core/jni/gnss/GnssPsds.cpp b/services/core/jni/gnss/GnssPsds.cpp
new file mode 100644
index 0000000..51a1450
--- /dev/null
+++ b/services/core/jni/gnss/GnssPsds.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Define LOG_TAG before <log/log.h> to overwrite the default value.
+#define LOG_TAG "GnssPsdsJni"
+
+#include "GnssPsds.h"
+
+#include "Utils.h"
+
+using android::hardware::hidl_bitfield;
+using android::hardware::gnss::PsdsType;
+using IGnssPsdsAidl = android::hardware::gnss::IGnssPsds;
+using IGnssPsdsHidl = android::hardware::gnss::V1_0::IGnssXtra;
+
+namespace android::gnss {
+
+// Implementation of GnssPsds (AIDL HAL)
+
+GnssPsdsAidl::GnssPsdsAidl(const sp<IGnssPsdsAidl>& iGnssPsds) : mIGnssPsds(iGnssPsds) {
+    assert(mIGnssPsds != nullptr);
+}
+
+jboolean GnssPsdsAidl::setCallback(const std::unique_ptr<GnssPsdsCallback>& callback) {
+    auto status = mIGnssPsds->setCallback(callback->getAidl());
+    return checkAidlStatus(status, "IGnssPsdsAidl setCallback() failed.");
+}
+
+void GnssPsdsAidl::injectPsdsData(const jbyteArray& data, const jint& length,
+                                  const jint& psdsType) {
+    JNIEnv* env = getJniEnv();
+    jbyte* bytes = reinterpret_cast<jbyte*>(env->GetPrimitiveArrayCritical(data, 0));
+    auto status = mIGnssPsds->injectPsdsData(static_cast<PsdsType>(psdsType),
+                                             std::vector<uint8_t>((const uint8_t*)bytes,
+                                                                  (const uint8_t*)bytes + length));
+    checkAidlStatus(status, "IGnssPsdsAidl injectPsdsData() failed.");
+    env->ReleasePrimitiveArrayCritical(data, bytes, JNI_ABORT);
+}
+
+// Implementation of GnssPsdsHidl
+
+GnssPsdsHidl::GnssPsdsHidl(const sp<android::hardware::gnss::V1_0::IGnssXtra>& iGnssXtra)
+      : mIGnssXtra(iGnssXtra) {
+    assert(mIGnssXtra != nullptr);
+}
+
+jboolean GnssPsdsHidl::setCallback(const std::unique_ptr<GnssPsdsCallback>& callback) {
+    auto result = mIGnssXtra->setCallback(callback->getHidl());
+    return checkHidlReturn(result, "IGnssPsdsHidl setCallback() failed.");
+}
+
+void GnssPsdsHidl::injectPsdsData(const jbyteArray& data, const jint& length, const jint&) {
+    JNIEnv* env = getJniEnv();
+    jbyte* bytes = reinterpret_cast<jbyte*>(env->GetPrimitiveArrayCritical(data, 0));
+    auto result = mIGnssXtra->injectXtraData(std::string((const char*)bytes, length));
+    checkHidlReturn(result, "IGnssXtra injectXtraData() failed.");
+    env->ReleasePrimitiveArrayCritical(data, bytes, JNI_ABORT);
+}
+
+} // namespace android::gnss
diff --git a/services/core/jni/gnss/GnssPsds.h b/services/core/jni/gnss/GnssPsds.h
new file mode 100644
index 0000000..6b65ee8
--- /dev/null
+++ b/services/core/jni/gnss/GnssPsds.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_SERVER_GNSS_GNSSPSDS_H
+#define _ANDROID_SERVER_GNSS_GNSSPSDS_H
+
+#pragma once
+
+#ifndef LOG_TAG
+#error LOG_TAG must be defined before including this file.
+#endif
+
+#include <android/hardware/gnss/1.0/IGnssXtra.h>
+#include <android/hardware/gnss/BnGnssPsds.h>
+#include <log/log.h>
+
+#include "GnssPsdsCallback.h"
+#include "jni.h"
+
+namespace android::gnss {
+
+class GnssPsdsInterface {
+public:
+    virtual ~GnssPsdsInterface() {}
+    virtual jboolean setCallback(const std::unique_ptr<GnssPsdsCallback>& callback);
+    virtual void injectPsdsData(const jbyteArray& data, const jint& length, const jint& psdsType);
+};
+
+class GnssPsdsAidl : public GnssPsdsInterface {
+public:
+    GnssPsdsAidl(const sp<android::hardware::gnss::IGnssPsds>& iGnssPsds);
+    jboolean setCallback(const std::unique_ptr<GnssPsdsCallback>& callback) override;
+    void injectPsdsData(const jbyteArray& data, const jint& length, const jint& psdsType) override;
+
+private:
+    const sp<android::hardware::gnss::IGnssPsds> mIGnssPsds;
+};
+
+class GnssPsdsHidl : public GnssPsdsInterface {
+public:
+    GnssPsdsHidl(const sp<android::hardware::gnss::V1_0::IGnssXtra>& iGnssXtra);
+    jboolean setCallback(const std::unique_ptr<GnssPsdsCallback>& callback) override;
+    void injectPsdsData(const jbyteArray& data, const jint& length, const jint& psdsType) override;
+
+private:
+    const sp<android::hardware::gnss::V1_0::IGnssXtra> mIGnssXtra;
+};
+
+} // namespace android::gnss
+
+#endif // _ANDROID_SERVER_GNSS_GNSSPSDS_H
diff --git a/services/core/jni/gnss/GnssPsdsCallback.cpp b/services/core/jni/gnss/GnssPsdsCallback.cpp
new file mode 100644
index 0000000..1dd7022
--- /dev/null
+++ b/services/core/jni/gnss/GnssPsdsCallback.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "GnssPsdsCbJni"
+
+#include "GnssPsdsCallback.h"
+
+#include <vector>
+
+#include "Utils.h"
+
+namespace android::gnss {
+
+namespace {
+jmethodID method_psdsDownloadRequest;
+} // anonymous namespace
+
+using binder::Status;
+using hardware::Return;
+using hardware::Void;
+using hardware::gnss::PsdsType;
+
+void GnssPsds_class_init_once(JNIEnv* env, jclass clazz) {
+    method_psdsDownloadRequest = env->GetMethodID(clazz, "psdsDownloadRequest", "(I)V");
+}
+
+// Implementation of android::hardware::gnss::IGnssPsdsCallback
+
+Status GnssPsdsCallbackAidl::downloadRequestCb(PsdsType psdsType) {
+    ALOGD("%s. psdsType: %d", __func__, static_cast<int32_t>(psdsType));
+    JNIEnv* env = getJniEnv();
+    env->CallVoidMethod(mCallbacksObj, method_psdsDownloadRequest, psdsType);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return Status::ok();
+}
+
+// Implementation of android::hardware::gnss::V1_0::IGnssPsdsCallback
+
+Return<void> GnssPsdsCallbackHidl::downloadRequestCb() {
+    JNIEnv* env = getJniEnv();
+    env->CallVoidMethod(mCallbacksObj, method_psdsDownloadRequest, /* psdsType= */ 1);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return Void();
+}
+
+} // namespace android::gnss
diff --git a/services/core/jni/gnss/GnssPsdsCallback.h b/services/core/jni/gnss/GnssPsdsCallback.h
new file mode 100644
index 0000000..3ac7473
--- /dev/null
+++ b/services/core/jni/gnss/GnssPsdsCallback.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_SERVER_GNSS_GNSSPSDSCALLBACK_H
+#define _ANDROID_SERVER_GNSS_GNSSPSDSCALLBACK_H
+
+#pragma once
+
+#ifndef LOG_TAG
+#error LOG_TAG must be defined before including this file.
+#endif
+
+#include <android/hardware/gnss/1.0/IGnssXtraCallback.h>
+#include <android/hardware/gnss/BnGnssPsdsCallback.h>
+#include <log/log.h>
+
+#include "jni.h"
+
+namespace android::gnss {
+
+namespace {
+extern jmethodID method_psdsDownloadRequest;
+} // anonymous namespace
+
+void GnssPsds_class_init_once(JNIEnv* env, jclass clazz);
+
+class GnssPsdsCallbackAidl : public hardware::gnss::BnGnssPsdsCallback {
+public:
+    GnssPsdsCallbackAidl() {}
+    binder::Status downloadRequestCb(hardware::gnss::PsdsType psdsType) override;
+};
+
+class GnssPsdsCallbackHidl : public hardware::gnss::V1_0::IGnssXtraCallback {
+public:
+    GnssPsdsCallbackHidl() {}
+    hardware::Return<void> downloadRequestCb() override;
+};
+
+class GnssPsdsCallback {
+public:
+    GnssPsdsCallback() {}
+    sp<GnssPsdsCallbackAidl> getAidl() {
+        if (callbackAidl == nullptr) {
+            callbackAidl = sp<GnssPsdsCallbackAidl>::make();
+        }
+        return callbackAidl;
+    }
+
+    sp<GnssPsdsCallbackHidl> getHidl() {
+        if (callbackHidl == nullptr) {
+            callbackHidl = sp<GnssPsdsCallbackHidl>::make();
+        }
+        return callbackHidl;
+    }
+
+private:
+    sp<GnssPsdsCallbackAidl> callbackAidl;
+    sp<GnssPsdsCallbackHidl> callbackHidl;
+};
+
+} // namespace android::gnss
+
+#endif // _ANDROID_SERVER_GNSS_GNSSPSDSCALLBACK_H
\ No newline at end of file
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 5aa3dfe..a70fd65 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -45,7 +45,8 @@
 import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
 import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
 import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER;
-import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_ID;
+import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_IDS;
+import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE;
 import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_DRAWABLE;
 import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_STRING;
 import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO;
@@ -7225,7 +7226,7 @@
             return;
         }
         Preconditions.checkCallAuthorization(
-                hasCallingOrSelfPermission(permission.SEND_LOST_MODE_LOCATION_UPDATES));
+                hasCallingOrSelfPermission(permission.TRIGGER_LOST_MODE));
 
         synchronized (getLockObject()) {
             final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
@@ -18692,10 +18693,10 @@
         sendResourceUpdatedBroadcast(EXTRA_RESOURCE_TYPE_STRING, stringIds);
     }
 
-    private void sendResourceUpdatedBroadcast(String resourceType, String[] resourceIds) {
+    private void sendResourceUpdatedBroadcast(int resourceType, String[] resourceIds) {
         final Intent intent = new Intent(ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
-        intent.putExtra(EXTRA_RESOURCE_ID, resourceIds);
-        intent.putExtra(resourceType, /* value= */ true);
+        intent.putExtra(EXTRA_RESOURCE_IDS, resourceIds);
+        intent.putExtra(EXTRA_RESOURCE_TYPE, resourceType);
         intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e895d37..5098abe 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -386,8 +386,8 @@
             "com.android.server.DeviceIdleController";
     private static final String BLOB_STORE_MANAGER_SERVICE_CLASS =
             "com.android.server.blob.BlobStoreManagerService";
-    private static final String APP_SEARCH_MANAGER_SERVICE_CLASS =
-            "com.android.server.appsearch.AppSearchManagerService";
+    private static final String APPSEARCH_MODULE_LIFECYCLE_CLASS =
+            "com.android.server.appsearch.AppSearchModule$Lifecycle";
     private static final String ISOLATED_COMPILATION_SERVICE_CLASS =
             "com.android.server.compos.IsolatedCompilationService";
     private static final String ROLLBACK_MANAGER_SERVICE_CLASS =
@@ -2764,8 +2764,8 @@
         mSystemServiceManager.startService(SAFETY_CENTER_SERVICE_CLASS);
         t.traceEnd();
 
-        t.traceBegin("AppSearchManagerService");
-        mSystemServiceManager.startService(APP_SEARCH_MANAGER_SERVICE_CLASS);
+        t.traceBegin("AppSearchModule");
+        mSystemServiceManager.startService(APPSEARCH_MODULE_LIFECYCLE_CLASS);
         t.traceEnd();
 
         if (SystemProperties.getBoolean("ro.config.isolated_compilation_enabled", false)) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index d2358a0..023608c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -1167,7 +1167,14 @@
         startUser(gameManagerService, USER_ID_1);
         gameManagerService.setGameMode(
                 mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
-        GameState gameState = new GameState(isLoading, GameState.MODE_NONE);
+        int testMode = GameState.MODE_NONE;
+        int testLabel = 99;
+        int testQuality = 123;
+        GameState gameState = new GameState(isLoading, testMode, testLabel, testQuality);
+        assertEquals(isLoading, gameState.isLoading());
+        assertEquals(testMode, gameState.getMode());
+        assertEquals(testLabel, gameState.getLabel());
+        assertEquals(testQuality, gameState.getQuality());
         gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
         mTestLooper.dispatchAll();
         verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, isLoading);
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 4e4854c..d8f409d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -213,7 +213,7 @@
     public void testProperties() {
         assertThat(mManager.getName()).isEqualTo(NAME);
         assertThat(mManager.getProperties()).isEqualTo(PROPERTIES);
-        assertThat(mManager.getIdentity()).isEqualTo(IDENTITY);
+        assertThat(mManager.getProviderIdentity()).isEqualTo(IDENTITY);
         assertThat(mManager.hasProvider()).isTrue();
 
         ProviderProperties newProperties = new ProviderProperties.Builder()
@@ -230,7 +230,7 @@
         CallerIdentity newIdentity = CallerIdentity.forTest(OTHER_USER, 1, "otherpackage",
                 "otherattribution");
         mProvider.setIdentity(newIdentity);
-        assertThat(mManager.getIdentity()).isEqualTo(newIdentity);
+        assertThat(mManager.getProviderIdentity()).isEqualTo(newIdentity);
 
         mManager.setRealProvider(null);
         assertThat(mManager.hasProvider()).isFalse();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index a9b7cfb..3ce2ed8 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -52,7 +52,6 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.wm.WindowManagerInternal;
@@ -94,7 +93,6 @@
     final FullScreenMagnificationController.ControllerContext mMockControllerCtx =
             mock(FullScreenMagnificationController.ControllerContext.class);
     final Context mMockContext = mock(Context.class);
-    final AccessibilityManagerService mMockAms = mock(AccessibilityManagerService.class);
     final AccessibilityTraceManager mMockTraceManager = mock(AccessibilityTraceManager.class);
     final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
     private final MagnificationAnimationCallback mAnimationCallback = mock(
@@ -121,12 +119,10 @@
         // Pretending ID of the Thread associated with looper as main thread ID in controller
         when(mMockContext.getMainLooper()).thenReturn(looper);
         when(mMockControllerCtx.getContext()).thenReturn(mMockContext);
-        when(mMockControllerCtx.getAms()).thenReturn(mMockAms);
         when(mMockControllerCtx.getTraceManager()).thenReturn(mMockTraceManager);
         when(mMockControllerCtx.getWindowManager()).thenReturn(mMockWindowManager);
         when(mMockControllerCtx.getHandler()).thenReturn(mMessageCapturingHandler);
         when(mMockControllerCtx.getAnimationDuration()).thenReturn(1000L);
-        when(mMockAms.getTraceManager()).thenReturn(mMockTraceManager);
         initMockWindowManager();
 
         mFullScreenMagnificationController = new FullScreenMagnificationController(
@@ -357,8 +353,8 @@
         assertEquals(newCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5);
         assertEquals(newCenter.y, mFullScreenMagnificationController.getCenterY(displayId), 0.5);
         assertThat(getCurrentMagnificationSpec(displayId), closeTo(endSpec));
-        verify(mMockAms).notifyMagnificationChanged(eq(displayId), eq(INITIAL_MAGNIFICATION_REGION),
-                mConfigCaptor.capture());
+        verify(mRequestObserver).onFullScreenMagnificationChanged(eq(displayId),
+                eq(INITIAL_MAGNIFICATION_REGION), mConfigCaptor.capture());
         assertConfigEquals(config, mConfigCaptor.getValue());
         verify(mMockValueAnimator).start();
         verify(mRequestObserver).onRequestMagnificationSpec(displayId, SERVICE_ID_1);
@@ -501,7 +497,7 @@
         mMessageCapturingHandler.sendAllMessages();
         MagnificationConfig config = buildConfig(1.0f, OTHER_MAGNIFICATION_BOUNDS.centerX(),
                 OTHER_MAGNIFICATION_BOUNDS.centerY());
-        verify(mMockAms).notifyMagnificationChanged(eq(displayId), eq(OTHER_REGION),
+        verify(mRequestObserver).onFullScreenMagnificationChanged(eq(displayId), eq(OTHER_REGION),
                 mConfigCaptor.capture());
         assertConfigEquals(config, mConfigCaptor.getValue());
     }
@@ -655,9 +651,9 @@
         register(displayId);
         zoomIn2xToMiddle(displayId);
         mMessageCapturingHandler.sendAllMessages();
-        reset(mMockAms);
+        reset(mRequestObserver);
         assertTrue(mFullScreenMagnificationController.resetIfNeeded(displayId, false));
-        verify(mMockAms).notifyMagnificationChanged(eq(displayId),
+        verify(mRequestObserver).onFullScreenMagnificationChanged(eq(displayId),
                 eq(INITIAL_MAGNIFICATION_REGION), any(MagnificationConfig.class));
         assertFalse(mFullScreenMagnificationController.isMagnifying(displayId));
         assertFalse(mFullScreenMagnificationController.resetIfNeeded(displayId, false));
@@ -676,8 +672,8 @@
         assertFalse(mFullScreenMagnificationController.reset(displayId, mAnimationCallback));
         mMessageCapturingHandler.sendAllMessages();
 
-        verify(mMockAms, never()).notifyMagnificationChanged(eq(displayId), any(Region.class),
-                any(MagnificationConfig.class));
+        verify(mRequestObserver, never()).onFullScreenMagnificationChanged(eq(displayId),
+                any(Region.class), any(MagnificationConfig.class));
         verify(mAnimationCallback).onResult(true);
     }
 
@@ -1072,8 +1068,8 @@
         when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
         mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
         verify(mMockWindowManager).setMagnificationSpec(eq(displayId), eq(startSpec));
-        verify(mMockAms).notifyMagnificationChanged(eq(displayId), eq(INITIAL_MAGNIFICATION_REGION),
-                mConfigCaptor.capture());
+        verify(mRequestObserver).onFullScreenMagnificationChanged(eq(displayId),
+                eq(INITIAL_MAGNIFICATION_REGION), mConfigCaptor.capture());
         assertConfigEquals(config, mConfigCaptor.getValue());
         Mockito.reset(mMockWindowManager);
 
@@ -1097,7 +1093,7 @@
 
         // Animation should have been restarted
         verify(mMockValueAnimator, times(2)).start();
-        verify(mMockAms, times(2)).notifyMagnificationChanged(eq(displayId),
+        verify(mRequestObserver, times(2)).onFullScreenMagnificationChanged(eq(displayId),
                 eq(INITIAL_MAGNIFICATION_REGION), mConfigCaptor.capture());
         assertConfigEquals(newConfig, mConfigCaptor.getValue());
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 2060223..0fed89b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -151,8 +151,6 @@
                 mock(FullScreenMagnificationController.ControllerContext.class);
         final WindowManagerInternal mockWindowManager = mock(WindowManagerInternal.class);
         when(mockController.getContext()).thenReturn(mContext);
-        when(mockController.getAms()).thenReturn(mMockAccessibilityManagerService);
-        when(mMockAccessibilityManagerService.getTraceManager()).thenReturn(mMockTraceManager);
         when(mockController.getTraceManager()).thenReturn(mMockTraceManager);
         when(mockController.getWindowManager()).thenReturn(mockWindowManager);
         when(mockController.getHandler()).thenReturn(new Handler(mContext.getMainLooper()));
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 3fcce92..ec59090 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -145,9 +145,10 @@
                         mCallbackDelegate, mTraceManager, mScaleProvider));
         mMockConnection = new MockWindowMagnificationConnection(true);
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        new FullScreenMagnificationControllerStubber(mScreenMagnificationController);
         mMagnificationController = new MagnificationController(mService, globalLock, mContext,
                 mScreenMagnificationController, mWindowMagnificationManager, mScaleProvider);
+        new FullScreenMagnificationControllerStubber(mScreenMagnificationController,
+                mMagnificationController);
 
         mMagnificationController.setMagnificationCapabilities(
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
@@ -451,6 +452,29 @@
     }
 
     @Test
+    public void onFullScreenMagnificationChanged_fullScreenEnabled_notifyMagnificationChanged()
+            throws RemoteException {
+        setMagnificationEnabled(MODE_FULLSCREEN);
+
+        final MagnificationConfig config = obtainMagnificationConfig(MODE_FULLSCREEN);
+        mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY,
+                config.getScale(), config.getCenterX(), config.getCenterY(),
+                true, TEST_SERVICE_ID);
+
+        // The first time is triggered when setting magnification enabled. And the second time is
+        // triggered when calling setScaleAndCenter.
+        final ArgumentCaptor<MagnificationConfig> configCaptor = ArgumentCaptor.forClass(
+                MagnificationConfig.class);
+        verify(mService, times(2)).notifyMagnificationChanged(eq(TEST_DISPLAY),
+                eq(FullScreenMagnificationControllerStubber.MAGNIFICATION_REGION),
+                configCaptor.capture());
+        final MagnificationConfig actualConfig = configCaptor.getValue();
+        assertEquals(config.getCenterX(), actualConfig.getCenterX(), 0);
+        assertEquals(config.getCenterY(), actualConfig.getCenterY(), 0);
+        assertEquals(config.getScale(), actualConfig.getScale(), 0);
+    }
+
+    @Test
     public void onAccessibilityActionPerformed_magnifierEnabled_showMagnificationButton()
             throws RemoteException {
         setMagnificationEnabled(MODE_WINDOW);
@@ -679,7 +703,7 @@
             throws RemoteException {
         setMagnificationEnabled(MODE_FULLSCREEN);
         mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY,
-                /* scale= */1, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y,
+                /* scale= */ 1, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y,
                 true, TEST_SERVICE_ID);
 
         mMagnificationController.onFullScreenMagnificationActivationState(TEST_DISPLAY, false);
@@ -884,6 +908,8 @@
     private static class FullScreenMagnificationControllerStubber {
         private static final Region MAGNIFICATION_REGION = new Region(0, 0, 500, 600);
         private final FullScreenMagnificationController mScreenMagnificationController;
+        private final FullScreenMagnificationController.MagnificationInfoChangedCallback
+                mMagnificationChangedCallback;
         private boolean mIsMagnifying = false;
         private float mScale = 1.0f;
         private float mCenterX = MAGNIFICATION_REGION.getBounds().exactCenterX();
@@ -891,8 +917,10 @@
         private int mServiceId = -1;
 
         FullScreenMagnificationControllerStubber(
-                FullScreenMagnificationController screenMagnificationController) {
+                FullScreenMagnificationController screenMagnificationController,
+                FullScreenMagnificationController.MagnificationInfoChangedCallback callback) {
             mScreenMagnificationController = screenMagnificationController;
+            mMagnificationChangedCallback = callback;
             stubMethods();
         }
 
@@ -930,6 +958,14 @@
                 } else {
                     reset();
                 }
+
+
+                final MagnificationConfig config = new MagnificationConfig.Builder().setMode(
+                        MODE_FULLSCREEN).setScale(mScale).setCenterX(mCenterX).setCenterY(
+                        mCenterY).build();
+                mMagnificationChangedCallback.onFullScreenMagnificationChanged(TEST_DISPLAY,
+                        FullScreenMagnificationControllerStubber.MAGNIFICATION_REGION,
+                        config);
                 return true;
             };
             doAnswer(setScaleAndCenterStubAnswer).when(
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index b601d14..18c6931 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -8459,7 +8459,7 @@
 
     @Test
     public void testSendLostModeLocationUpdate_notOrganizationOwnedDevice() {
-        mContext.callerPermissions.add(permission.SEND_LOST_MODE_LOCATION_UPDATES);
+        mContext.callerPermissions.add(permission.TRIGGER_LOST_MODE);
         assertThrows(IllegalStateException.class, () -> dpm.sendLostModeLocationUpdate(
                 getServices().executor, /* empty callback */ result -> {}));
     }
@@ -8467,7 +8467,7 @@
     @Test
     public void testSendLostModeLocationUpdate_asDeviceOwner() throws Exception {
         final String TEST_PROVIDER = "network";
-        mContext.callerPermissions.add(permission.SEND_LOST_MODE_LOCATION_UPDATES);
+        mContext.callerPermissions.add(permission.TRIGGER_LOST_MODE);
         setDeviceOwner();
         when(getServices().locationManager.getAllProviders()).thenReturn(List.of(TEST_PROVIDER));
         when(getServices().locationManager.isProviderEnabled(TEST_PROVIDER)).thenReturn(true);
@@ -8484,7 +8484,7 @@
         final int MANAGED_PROFILE_ADMIN_UID =
                 UserHandle.getUid(CALLER_USER_HANDLE, DpmMockContext.SYSTEM_UID);
         mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
-        mContext.callerPermissions.add(permission.SEND_LOST_MODE_LOCATION_UPDATES);
+        mContext.callerPermissions.add(permission.TRIGGER_LOST_MODE);
         addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
         configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
         when(getServices().locationManager.getAllProviders()).thenReturn(List.of(TEST_PROVIDER));
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
index ab21ab0..03363a1 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
@@ -71,9 +71,11 @@
 
     private static final String PACKAGE_NAME = getBits(AtomicFormula.PACKAGE_NAME, KEY_BITS);
     private static final String APP_CERTIFICATE = getBits(AtomicFormula.APP_CERTIFICATE, KEY_BITS);
+    private static final String APP_CERTIFICATE_LINEAGE =
+            getBits(AtomicFormula.APP_CERTIFICATE_LINEAGE, KEY_BITS);
     private static final String VERSION_CODE = getBits(AtomicFormula.VERSION_CODE, KEY_BITS);
     private static final String PRE_INSTALLED = getBits(AtomicFormula.PRE_INSTALLED, KEY_BITS);
-    private static final int INVALID_KEY_VALUE = 8;
+    private static final int INVALID_KEY_VALUE = 9;
     private static final String INVALID_KEY = getBits(INVALID_KEY_VALUE, KEY_BITS);
 
     private static final String EQ = getBits(AtomicFormula.EQ, OPERATOR_BITS);
@@ -337,6 +339,40 @@
     }
 
     @Test
+    public void testBinaryString_validAtomicFormulaWithCertificateLineage() throws Exception {
+        String appCertificate = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+        String ruleBits =
+                START_BIT
+                        + ATOMIC_FORMULA_START_BITS
+                        + APP_CERTIFICATE_LINEAGE
+                        + EQ
+                        + IS_HASHED
+                        + getBits(appCertificate.length(), VALUE_SIZE_BITS)
+                        + getValueBits(appCertificate)
+                        + DENY
+                        + END_BIT;
+        byte[] ruleBytes = getBytes(ruleBits);
+        ByteBuffer rule =
+                ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
+        rule.put(DEFAULT_FORMAT_VERSION_BYTES);
+        rule.put(ruleBytes);
+
+        RuleParser binaryParser = new RuleBinaryParser();
+        Rule expectedRule =
+                new Rule(
+                        new AtomicFormula.StringAtomicFormula(
+                                AtomicFormula.APP_CERTIFICATE_LINEAGE,
+                                IntegrityUtils.getHexDigest(
+                                        appCertificate.getBytes(StandardCharsets.UTF_8)),
+                                /* isHashedValue= */ true),
+                        Rule.DENY);
+
+        List<Rule> rules = binaryParser.parse(rule.array());
+
+        assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
+    }
+
+    @Test
     public void testBinaryString_validAtomicFormula_integerValue_noIndexing() throws Exception {
         int versionCode = 1;
         String ruleBits =
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
index c771000..0287510 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -41,7 +41,9 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
+import android.os.HandlerThread;
 import android.os.LocaleList;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.SimpleClock;
 import android.util.SparseArray;
@@ -78,6 +80,7 @@
  */
 @RunWith(AndroidJUnit4.class)
 public class LocaleManagerBackupRestoreTest {
+    private static final String TAG = "LocaleManagerBackupRestoreTest";
     private static final String DEFAULT_PACKAGE_NAME = "com.android.myapp";
     private static final String DEFAULT_LOCALE_TAGS = "en-XC,ar-XB";
     private static final String TEST_LOCALES_XML_TAG = "locales";
@@ -131,12 +134,17 @@
 
         doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
 
+        HandlerThread broadcastHandlerThread = new HandlerThread(TAG,
+                Process.THREAD_PRIORITY_BACKGROUND);
+        broadcastHandlerThread.start();
+
         mBackupHelper = spy(new ShadowLocaleManagerBackupHelper(mMockContext,
-                mMockLocaleManagerService, mMockPackageManagerInternal, mClock, STAGE_DATA));
+                mMockLocaleManagerService, mMockPackageManagerInternal, mClock, STAGE_DATA,
+                broadcastHandlerThread));
         doNothing().when(mBackupHelper).notifyBackupManager();
 
         mUserMonitor = mBackupHelper.getUserMonitor();
-        mPackageMonitor = mBackupHelper.getPackageMonitor();
+        mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper);
         setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
index ca5b0cb..0b3ef45 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -46,6 +46,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import com.android.internal.content.PackageMonitor;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal.PackageConfig;
 
@@ -86,6 +87,8 @@
     private ActivityTaskManagerInternal mMockActivityTaskManager;
     @Mock
     private ActivityManagerInternal mMockActivityManager;
+    @Mock
+    PackageMonitor mMockPackageMonitor;
 
     @Before
     public void setUp() throws Exception {
@@ -93,6 +96,7 @@
         mMockActivityTaskManager = mock(ActivityTaskManagerInternal.class);
         mMockActivityManager = mock(ActivityManagerInternal.class);
         mMockPackageManagerInternal = mock(PackageManagerInternal.class);
+        mMockPackageMonitor = mock(PackageMonitor.class);
 
         // For unit tests, set the default installer info
         PackageManager mockPackageManager = mock(PackageManager.class);
@@ -113,7 +117,8 @@
 
         mMockBackupHelper = mock(ShadowLocaleManagerBackupHelper.class);
         mLocaleManagerService = new LocaleManagerService(mMockContext, mMockActivityTaskManager,
-                mMockActivityManager, mMockPackageManagerInternal, mMockBackupHelper);
+                mMockActivityManager, mMockPackageManagerInternal,
+                mMockBackupHelper, mMockPackageMonitor);
     }
 
     @Test(expected = SecurityException.class)
diff --git a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
index b0fc636..ad9be0d 100644
--- a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
+++ b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
+import android.os.HandlerThread;
 import android.util.SparseArray;
 
 import java.time.Clock;
@@ -31,7 +32,8 @@
     ShadowLocaleManagerBackupHelper(Context context,
             LocaleManagerService localeManagerService,
             PackageManagerInternal pmInternal, Clock clock,
-            SparseArray<LocaleManagerBackupHelper.StagedData> stagedData) {
-        super(context, localeManagerService, pmInternal, clock, stagedData);
+            SparseArray<LocaleManagerBackupHelper.StagedData> stagedData,
+            HandlerThread broadcastHandlerThread) {
+        super(context, localeManagerService, pmInternal, clock, stagedData, broadcastHandlerThread);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index e1a4989..3d24a81 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -61,8 +61,6 @@
 
 import androidx.test.InstrumentationRegistry;
 
-import com.android.internal.app.IBatteryStats;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -106,8 +104,6 @@
     @Mock
     private IBinder mVibrationToken;
     @Mock
-    private IBatteryStats mIBatteryStatsMock;
-    @Mock
     private VibrationConfig mVibrationConfigMock;
 
     private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
@@ -117,6 +113,9 @@
     private TestLooper mTestLooper;
     private TestLooperAutoDispatcher mCustomTestLooperDispatcher;
 
+    // Setup from the providers when VibrationThread is initialized.
+    private SparseArray<VibratorController> mControllers;
+
     @Before
     public void setUp() throws Exception {
         mTestLooper = new TestLooper();
@@ -178,11 +177,11 @@
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
 
         assertEquals(Arrays.asList(expectedOneShot(10)),
                 mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
@@ -197,11 +196,11 @@
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
 
         assertEquals(Arrays.asList(expectedOneShot(10)),
                 mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
@@ -219,11 +218,11 @@
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(15L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(15L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
 
         assertEquals(Arrays.asList(expectedOneShot(15)),
                 mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
@@ -247,15 +246,15 @@
                         thread, TEST_TIMEOUT_MILLIS));
         // Vibration still running after 2 cycles.
         assertTrue(thread.isAlive());
-        assertTrue(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
+        assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
 
         thread.cancel();
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), anyLong());
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
-        assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
 
         List<Float> playedAmplitudes = fakeVibrator.getAmplitudes();
         assertFalse(fakeVibrator.getEffectSegments().isEmpty());
@@ -284,7 +283,7 @@
         waitForCompletion(thread);
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
-        assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
         assertEquals(Arrays.asList(expectedOneShot(1000)), fakeVibrator.getEffectSegments());
     }
 
@@ -306,7 +305,7 @@
         waitForCompletion(thread);
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
-        assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
         assertEquals(Arrays.asList(expectedOneShot(5550)), fakeVibrator.getEffectSegments());
     }
 
@@ -329,7 +328,7 @@
         waitForCompletion(thread);
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
-        assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
         assertEquals(2, fakeVibrator.getEffectSegments().size());
         // First time turn vibrator ON for minimum of 1s.
         assertEquals(1000L, fakeVibrator.getEffectSegments().get(0).getDuration());
@@ -354,7 +353,7 @@
                 .compose();
         VibrationThread vibrationThread = startThreadAndDispatcher(vibrationId, effect);
 
-        assertTrue(waitUntil(t -> t.getVibrators().get(VIBRATOR_ID).isVibrating(), vibrationThread,
+        assertTrue(waitUntil(t -> mControllers.get(VIBRATOR_ID).isVibrating(), vibrationThread,
                 TEST_TIMEOUT_MILLIS));
         assertTrue(vibrationThread.isAlive());
 
@@ -367,7 +366,7 @@
         waitForCompletion(cancellingThread);
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
-        assertFalse(vibrationThread.getVibrators().get(VIBRATOR_ID).isVibrating());
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
     }
 
     @Test
@@ -379,7 +378,7 @@
         VibrationEffect effect = VibrationEffect.createWaveform(new long[]{100}, new int[]{100}, 0);
         VibrationThread vibrationThread = startThreadAndDispatcher(vibrationId, effect);
 
-        assertTrue(waitUntil(t -> t.getVibrators().get(VIBRATOR_ID).isVibrating(), vibrationThread,
+        assertTrue(waitUntil(t -> mControllers.get(VIBRATOR_ID).isVibrating(), vibrationThread,
                 TEST_TIMEOUT_MILLIS));
         assertTrue(vibrationThread.isAlive());
 
@@ -392,7 +391,7 @@
         waitForCompletion(cancellingThread);
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
-        assertFalse(vibrationThread.getVibrators().get(VIBRATOR_ID).isVibrating());
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
     }
 
     @Test
@@ -404,11 +403,11 @@
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
 
         assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_THUD)),
                 mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
@@ -427,11 +426,11 @@
         VibrationThread thread = startThreadAndDispatcher(vibration);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
 
         assertEquals(Arrays.asList(expectedOneShot(10)),
                 mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
@@ -446,8 +445,8 @@
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock, never()).noteVibratorOn(eq(UID), anyLong());
-        verify(mIBatteryStatsMock, never()).noteVibratorOff(eq(UID));
+        verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong());
+        verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
         assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
@@ -466,11 +465,11 @@
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(40L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(40L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
         assertEquals(Arrays.asList(
                 expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
                 expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0)),
@@ -486,8 +485,8 @@
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock, never()).noteVibratorOn(eq(UID), anyLong());
-        verify(mIBatteryStatsMock, never()).noteVibratorOff(eq(UID));
+        verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong());
+        verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
         assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
@@ -536,11 +535,11 @@
         waitForCompletion(thread);
 
         // Use first duration the vibrator is turned on since we cannot estimate the clicks.
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
         assertEquals(Arrays.asList(
                 expectedOneShot(10),
                 expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
@@ -573,11 +572,11 @@
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(100L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
         assertEquals(Arrays.asList(
                 expectedRamp(/* amplitude= */ 1, /* frequencyHz= */ 150, /* duration= */ 10),
                 expectedRamp(/* startAmplitude= */ 1, /* endAmplitude= */ 0,
@@ -630,11 +629,11 @@
                 TEST_TIMEOUT_MILLIS));
         // Vibration still running after 2 cycles.
         assertTrue(thread.isAlive());
-        assertTrue(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
+        assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
 
         thread.binderDied();
         waitForCompletion(thread);
-        assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
     }
@@ -666,12 +665,12 @@
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
 
         assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_TICK)),
                 mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
@@ -690,15 +689,15 @@
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
         verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
         verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(thread.getVibrators().get(1).isVibrating());
-        assertFalse(thread.getVibrators().get(2).isVibrating());
-        assertFalse(thread.getVibrators().get(3).isVibrating());
+        assertFalse(mControllers.get(1).isVibrating());
+        assertFalse(mControllers.get(2).isVibrating());
+        assertFalse(mControllers.get(3).isVibrating());
 
         VibrationEffectSegment expected = expectedPrebaked(VibrationEffect.EFFECT_CLICK);
         assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffectSegments());
@@ -728,17 +727,17 @@
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
         verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
         verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
         verify(mControllerCallbacks).onComplete(eq(4), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(thread.getVibrators().get(1).isVibrating());
-        assertFalse(thread.getVibrators().get(2).isVibrating());
-        assertFalse(thread.getVibrators().get(3).isVibrating());
-        assertFalse(thread.getVibrators().get(4).isVibrating());
+        assertFalse(mControllers.get(1).isVibrating());
+        assertFalse(mControllers.get(2).isVibrating());
+        assertFalse(mControllers.get(3).isVibrating());
+        assertFalse(mControllers.get(4).isVibrating());
 
         assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
                 mVibratorProviders.get(1).getEffectSegments());
@@ -777,18 +776,18 @@
         controllerVerifier.verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
         controllerVerifier.verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
 
-        InOrder batterVerifier = inOrder(mIBatteryStatsMock);
-        batterVerifier.verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
-        batterVerifier.verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
-        batterVerifier.verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
-        batterVerifier.verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
-        batterVerifier.verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
-        batterVerifier.verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        InOrder batteryVerifier = inOrder(mManagerHooks);
+        batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+        batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
+        batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
+        batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
+        batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+        batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(thread.getVibrators().get(1).isVibrating());
-        assertFalse(thread.getVibrators().get(2).isVibrating());
-        assertFalse(thread.getVibrators().get(3).isVibrating());
+        assertFalse(mControllers.get(1).isVibrating());
+        assertFalse(mControllers.get(2).isVibrating());
+        assertFalse(mControllers.get(3).isVibrating());
 
         assertEquals(Arrays.asList(expectedOneShot(10)),
                 mVibratorProviders.get(1).getEffectSegments());
@@ -945,22 +944,22 @@
 
         // All vibrators are turned on in parallel.
         assertTrue(waitUntil(
-                t -> t.getVibrators().get(1).isVibrating()
-                        && t.getVibrators().get(2).isVibrating()
-                        && t.getVibrators().get(3).isVibrating(),
+                t -> mControllers.get(1).isVibrating()
+                        && mControllers.get(2).isVibrating()
+                        && mControllers.get(3).isVibrating(),
                 thread, TEST_TIMEOUT_MILLIS));
 
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(80L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(80L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
         verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
         verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        assertFalse(thread.getVibrators().get(1).isVibrating());
-        assertFalse(thread.getVibrators().get(2).isVibrating());
-        assertFalse(thread.getVibrators().get(3).isVibrating());
+        assertFalse(mControllers.get(1).isVibrating());
+        assertFalse(mControllers.get(2).isVibrating());
+        assertFalse(mControllers.get(3).isVibrating());
 
         assertEquals(Arrays.asList(expectedOneShot(25)),
                 mVibratorProviders.get(1).getEffectSegments());
@@ -1035,7 +1034,7 @@
         // After the vibrator call ends the vibration is cancelled and the vibrator is turned off.
         waitForCompletion(vibrationThread, /* timeout= */ latency + TEST_TIMEOUT_MILLIS);
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
-        assertFalse(vibrationThread.getVibrators().get(VIBRATOR_ID).isVibrating());
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
     }
 
     @Test
@@ -1055,7 +1054,7 @@
                 .combine();
         VibrationThread vibrationThread = startThreadAndDispatcher(vibrationId, effect);
 
-        assertTrue(waitUntil(t -> t.getVibrators().get(2).isVibrating(), vibrationThread,
+        assertTrue(waitUntil(t -> mControllers.get(2).isVibrating(), vibrationThread,
                 TEST_TIMEOUT_MILLIS));
         assertTrue(vibrationThread.isAlive());
 
@@ -1068,8 +1067,8 @@
         waitForCompletion(cancellingThread);
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
-        assertFalse(vibrationThread.getVibrators().get(1).isVibrating());
-        assertFalse(vibrationThread.getVibrators().get(2).isVibrating());
+        assertFalse(mControllers.get(1).isVibrating());
+        assertFalse(mControllers.get(2).isVibrating());
     }
 
     @Test
@@ -1086,8 +1085,8 @@
                 .combine();
         VibrationThread vibrationThread = startThreadAndDispatcher(vibrationId, effect);
 
-        assertTrue(waitUntil(t -> t.getVibrators().get(1).isVibrating()
-                        && t.getVibrators().get(2).isVibrating(),
+        assertTrue(waitUntil(t -> mControllers.get(1).isVibrating()
+                        && mControllers.get(2).isVibrating(),
                 vibrationThread, TEST_TIMEOUT_MILLIS));
         assertTrue(vibrationThread.isAlive());
 
@@ -1100,8 +1099,8 @@
         waitForCompletion(cancellingThread);
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
-        assertFalse(vibrationThread.getVibrators().get(1).isVibrating());
-        assertFalse(vibrationThread.getVibrators().get(2).isVibrating());
+        assertFalse(mControllers.get(1).isVibrating());
+        assertFalse(mControllers.get(2).isVibrating());
     }
 
     @Test
@@ -1110,7 +1109,7 @@
         VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5}, new int[]{100}, 0);
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
 
-        assertTrue(waitUntil(t -> t.getVibrators().get(VIBRATOR_ID).isVibrating(), thread,
+        assertTrue(waitUntil(t -> mControllers.get(VIBRATOR_ID).isVibrating(), thread,
                 TEST_TIMEOUT_MILLIS));
         assertTrue(thread.isAlive());
 
@@ -1121,7 +1120,7 @@
         verify(mVibrationToken).unlinkToDeath(same(thread), eq(0));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
         assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
-        assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
     }
 
     @Test
@@ -1192,7 +1191,7 @@
         long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.createOneShot(10_000, 240);
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
-        assertTrue(waitUntil(t -> t.getVibrators().get(VIBRATOR_ID).isVibrating(), thread,
+        assertTrue(waitUntil(t -> mControllers.get(VIBRATOR_ID).isVibrating(), thread,
                 TEST_TIMEOUT_MILLIS));
         thread.cancel();
         waitForCompletion(thread);
@@ -1299,8 +1298,9 @@
     }
 
     private VibrationThread startThreadAndDispatcher(Vibration vib) {
+        mControllers = createVibratorControllers();
         VibrationThread thread = new VibrationThread(vib, mVibrationSettings, mEffectAdapter,
-                createVibratorControllers(), mWakeLock, mIBatteryStatsMock, mManagerHooks);
+                mControllers, mWakeLock, mManagerHooks);
         doAnswer(answer -> {
             thread.vibratorComplete(answer.getArgument(0));
             return null;
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index ccdb105..19111e5 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -71,6 +71,7 @@
 import android.os.test.TestLooper;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.VibrationConfig;
 import android.os.vibrator.VibrationEffectSegment;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
@@ -78,6 +79,7 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.internal.app.IBatteryStats;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.LocalServices;
@@ -144,6 +146,8 @@
     private AppOpsManager mAppOpsManagerMock;
     @Mock
     private IInputManager mIInputManagerMock;
+    @Mock
+    private IBatteryStats mBatteryStatsMock;
 
     private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
 
@@ -152,12 +156,14 @@
     private FakeVibrator mVibrator;
     private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
     private VibratorManagerService.ExternalVibratorService mExternalVibratorService;
+    private VibrationConfig mVibrationConfig;
 
     @Before
     public void setUp() throws Exception {
         mTestLooper = new TestLooper();
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
         InputManager inputManager = InputManager.resetInstance(mIInputManagerMock);
+        mVibrationConfig = new VibrationConfig(mContextSpy.getResources());
 
         ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
         when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
@@ -222,6 +228,11 @@
                     }
 
                     @Override
+                    IBatteryStats getBatteryStatsService() {
+                        return mBatteryStatsMock;
+                    }
+
+                    @Override
                     VibratorController createVibratorController(int vibratorId,
                             VibratorController.OnVibrationCompleteListener listener) {
                         return mVibratorProviders.get(vibratorId)
@@ -382,6 +393,11 @@
         inOrderVerifier.verify(listenerMock).onVibrating(eq(true));
         inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
         inOrderVerifier.verifyNoMoreInteractions();
+
+        InOrder batteryVerifier = inOrder(mBatteryStatsMock);
+        batteryVerifier.verify(mBatteryStatsMock)
+                .noteVibratorOn(UID, 40 + mVibrationConfig.getRampDownDurationMs());
+        batteryVerifier.verify(mBatteryStatsMock).noteVibratorOff(UID);
     }
 
     @Test
@@ -731,6 +747,12 @@
         // Wait before checking it never played a second effect.
         assertFalse(waitUntil(s -> mVibratorProviders.get(1).getEffectSegments().size() > 1,
                 service, /* timeout= */ 50));
+
+        // The time estimate is recorded when the vibration starts, repeating vibrations
+        // are capped at BATTERY_STATS_REPEATING_VIBRATION_DURATION (=5000).
+        verify(mBatteryStatsMock).noteVibratorOn(UID, 5000);
+        // The second vibration shouldn't have recorded that the vibrators were turned on.
+        verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java
index 2794d48..c64ff9e 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java
@@ -114,7 +114,7 @@
     }
 
     @Test
-    public void testSensorChanged_normalCase2() {
+    public void testOnSensorChanged_normalCase2() {
         mWindowOrientationListener.mOrientationJudge.onSensorChanged(mFakeSensorEvent);
 
         mFakeRotationResolverInternal.callbackWithFailureResult(
@@ -123,6 +123,15 @@
         assertThat(mFinalizedRotation).isEqualTo(DEFAULT_SENSOR_ROTATION);
     }
 
+    @Test
+    public void testOnSensorChanged_rotationResolverServiceIsNull_useSensorResult() {
+        mWindowOrientationListener.mRotationResolverService = null;
+
+        mWindowOrientationListener.mOrientationJudge.onSensorChanged(mFakeSensorEvent);
+
+        assertThat(mFinalizedRotation).isEqualTo(DEFAULT_SENSOR_ROTATION);
+    }
+
     static final class TestableRotationResolver extends RotationResolverInternal {
         @Surface.Rotation
         RotationResolverCallbackInternal mCallback;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
index bd7186e..a7d18ee 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
@@ -126,7 +126,7 @@
             when(file.getAbsolutePath()).thenReturn(String.valueOf(i));
             AtomicFile af = new AtomicFile(file);
             expectedFiles.add(af);
-            mDataBase.mHistoryFiles.addLast(af);
+            mDataBase.mHistoryFiles.add(af);
         }
 
         cal.add(Calendar.DATE, -1 * retainDays);
@@ -136,7 +136,7 @@
             when(file.getName()).thenReturn(String.valueOf(cal.getTimeInMillis() - i));
             when(file.getAbsolutePath()).thenReturn(String.valueOf(cal.getTimeInMillis() - i));
             AtomicFile af = new AtomicFile(file);
-            mDataBase.mHistoryFiles.addLast(af);
+            mDataBase.mHistoryFiles.add(af);
         }
 
         // back to today; trim everything a day + old
@@ -162,7 +162,7 @@
             when(file.getName()).thenReturn(i + ".bak");
             when(file.getAbsolutePath()).thenReturn(i + ".bak");
             AtomicFile af = new AtomicFile(file);
-            mDataBase.mHistoryFiles.addLast(af);
+            mDataBase.mHistoryFiles.add(af);
         }
 
         // trim everything a day+ old
@@ -224,7 +224,7 @@
     public void testReadNotificationHistory_readsAllFiles() throws Exception {
         for (long i = 10; i >= 5; i--) {
             AtomicFile af = mock(AtomicFile.class);
-            mDataBase.mHistoryFiles.addLast(af);
+            mDataBase.mHistoryFiles.add(af);
         }
 
         mDataBase.readNotificationHistory();
@@ -248,11 +248,11 @@
     public void testReadNotificationHistory_withNumFilterDoesNotReadExtraFiles() throws Exception {
         AtomicFile af = mock(AtomicFile.class);
         when(af.getBaseFile()).thenReturn(new File(mRootDir, "af"));
-        mDataBase.mHistoryFiles.addLast(af);
+        mDataBase.mHistoryFiles.add(af);
 
         AtomicFile af2 = mock(AtomicFile.class);
         when(af2.getBaseFile()).thenReturn(new File(mRootDir, "af2"));
-        mDataBase.mHistoryFiles.addLast(af2);
+        mDataBase.mHistoryFiles.add(af2);
 
         mDataBase.readNotificationHistory(null, null, 0);
 
@@ -269,7 +269,7 @@
 
         AtomicFile af = mock(AtomicFile.class);
         when(af.getBaseFile()).thenReturn(new File(mRootDir, "af"));
-        mDataBase.mHistoryFiles.addLast(af);
+        mDataBase.mHistoryFiles.add(af);
 
         when(nh.removeNotificationFromWrite("pkg", 123)).thenReturn(true);
 
@@ -292,7 +292,7 @@
 
         AtomicFile af = mock(AtomicFile.class);
         when(af.getBaseFile()).thenReturn(new File(mRootDir, "af"));
-        mDataBase.mHistoryFiles.addLast(af);
+        mDataBase.mHistoryFiles.add(af);
 
         when(nh.removeNotificationFromWrite("pkg", 123)).thenReturn(false);
 
@@ -315,7 +315,7 @@
 
         AtomicFile af = mock(AtomicFile.class);
         when(af.getBaseFile()).thenReturn(new File(mRootDir, "af"));
-        mDataBase.mHistoryFiles.addLast(af);
+        mDataBase.mHistoryFiles.add(af);
 
         when(nh.removeConversationsFromWrite("pkg", Set.of("convo", "another"))).thenReturn(true);
 
@@ -338,7 +338,7 @@
 
         AtomicFile af = mock(AtomicFile.class);
         when(af.getBaseFile()).thenReturn(new File(mRootDir, "af"));
-        mDataBase.mHistoryFiles.addLast(af);
+        mDataBase.mHistoryFiles.add(af);
 
         when(nh.removeConversationsFromWrite("pkg", Set.of("convo"))).thenReturn(false);
 
@@ -361,7 +361,7 @@
 
         AtomicFile af = mock(AtomicFile.class);
         when(af.getBaseFile()).thenReturn(new File(mRootDir, "af"));
-        mDataBase.mHistoryFiles.addLast(af);
+        mDataBase.mHistoryFiles.add(af);
 
         when(nh.removeChannelFromWrite("pkg", "channel")).thenReturn(true);
 
@@ -384,7 +384,7 @@
 
         AtomicFile af = mock(AtomicFile.class);
         when(af.getBaseFile()).thenReturn(new File(mRootDir, "af"));
-        mDataBase.mHistoryFiles.addLast(af);
+        mDataBase.mHistoryFiles.add(af);
 
         when(nh.removeChannelFromWrite("pkg", "channel")).thenReturn(false);
 
@@ -424,7 +424,7 @@
         for (int i = 0; i < 5; i++) {
             AtomicFile af = mock(AtomicFile.class);
             when(af.getBaseFile()).thenReturn(new File(mRootDir, "af" + i));
-            mDataBase.mHistoryFiles.addLast(af);
+            mDataBase.mHistoryFiles.add(af);
         }
         // Baseline size of history files
         assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(5);
@@ -440,7 +440,7 @@
         for (int i = 0; i < 5; i++) {
             AtomicFile af = mock(AtomicFile.class);
             when(af.getBaseFile()).thenReturn(new File(mRootDir, "af" + i));
-            mDataBase.mHistoryFiles.addLast(af);
+            mDataBase.mHistoryFiles.add(af);
         }
         // Baseline size of history files
         assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(5);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 87abc53..16c5bfe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -925,14 +925,10 @@
                 any(), anyBoolean(), anyBoolean(), eq(false));
     }
 
-    private ActivityRecord createSingleTaskActivityOn(Task stack) {
+    private ActivityRecord createSingleTaskActivityOn(Task task) {
         final ComponentName componentName = ComponentName.createRelative(
                 DEFAULT_COMPONENT_PACKAGE_NAME,
                 DEFAULT_COMPONENT_PACKAGE_NAME + ".SingleTaskActivity");
-        final Task task = new TaskBuilder(mSupervisor)
-                .setComponent(componentName)
-                .setParentTaskFragment(stack)
-                .build();
         return new ActivityBuilder(mAtm)
                 .setComponent(componentName)
                 .setLaunchMode(LAUNCH_SINGLE_TASK)
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 0d67946..72521fd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -120,7 +120,7 @@
         behind.setState(ActivityRecord.State.STARTED, "test");
         behind.mVisibleRequested = true;
 
-        task.performClearTask("test");
+        task.removeActivities("test", false /* excludingTaskOverlay */);
         assertFalse(mDisplayContent.mAppTransition.isReady());
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
index cc1869e..9fc9489 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
@@ -557,7 +557,7 @@
         mLockTaskController.updateLockTaskPackages(TEST_USER_ID, allowlist);
 
         // THEN the task running that package should be stopped
-        verify(tr2).performClearTaskLocked();
+        verify(tr2).performClearTaskForReuse(false /* excludingTaskOverlay*/);
         assertFalse(mLockTaskController.isTaskLocked(tr2));
         // THEN the other task should remain locked
         assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState());
@@ -569,7 +569,7 @@
         mLockTaskController.updateLockTaskPackages(TEST_USER_ID, allowlist);
 
         // THEN the last task should be cleared, and the system should quit LockTask mode
-        verify(tr1).performClearTaskLocked();
+        verify(tr1).performClearTaskForReuse(false /* excludingTaskOverlay*/);
         assertFalse(mLockTaskController.isTaskLocked(tr1));
         assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState());
         verifyLockTaskStopped(times(1));
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index ba65104..acceadf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -938,7 +938,33 @@
     }
 
     @Test
-    public void testGetValidLaunchRootTaskOnDisplayWithCandidateRootTask() {
+    public void testGetLaunchRootTaskOnSecondaryTaskDisplayArea() {
+        // Adding another TaskDisplayArea to the default display.
+        final DisplayContent display = mRootWindowContainer.getDefaultDisplay();
+        final TaskDisplayArea taskDisplayArea = new TaskDisplayArea(display,
+                mWm, "TDA", FEATURE_VENDOR_FIRST);
+        display.addChild(taskDisplayArea, POSITION_BOTTOM);
+
+        // Making sure getting the root task from the preferred TDA
+        LaunchParamsController.LaunchParams launchParams =
+                new LaunchParamsController.LaunchParams();
+        launchParams.mPreferredTaskDisplayArea = taskDisplayArea;
+        Task root = mRootWindowContainer.getLaunchRootTask(null /* r */, null /* options */,
+                null /* candidateTask */, null /* sourceTask */, true /* onTop */, launchParams,
+                0 /* launchParams */);
+        assertEquals(taskDisplayArea, root.getTaskDisplayArea());
+
+        // Making sure still getting the root task from the preferred TDA when passing in a
+        // launching activity.
+        ActivityRecord r = new ActivityBuilder(mAtm).build();
+        root = mRootWindowContainer.getLaunchRootTask(r, null /* options */,
+                null /* candidateTask */, null /* sourceTask */, true /* onTop */, launchParams,
+                0 /* launchParams */);
+        assertEquals(taskDisplayArea, root.getTaskDisplayArea());
+    }
+
+    @Test
+    public void testGetOrCreateRootTaskOnDisplayWithCandidateRootTask() {
         // Create a root task with an activity on secondary display.
         final TestDisplayContent secondaryDisplay = new TestDisplayContent.Builder(mAtm, 300,
                 600).build();
@@ -947,9 +973,9 @@
         final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
 
         // Make sure the root task is valid and can be reused on default display.
-        final Task rootTask = mRootWindowContainer.getValidLaunchRootTaskInTaskDisplayArea(
-                mRootWindowContainer.getDefaultTaskDisplayArea(), activity, task,
-                null /* options */, null /* launchParams */);
+        final Task rootTask = mRootWindowContainer.getDefaultTaskDisplayArea().getOrCreateRootTask(
+                activity, null /* options */, task, null /* sourceTask */, null /* launchParams */,
+                0 /* launchFlags */, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         assertEquals(task, rootTask);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index 168c250..c0759c1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -575,7 +575,7 @@
         final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FULLSCREEN);
         final ActivityRecord source = createSourceActivity(fullscreenDisplay);
-        source.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        source.getTask().setWindowingMode(WINDOWING_MODE_FREEFORM);
 
         assertEquals(RESULT_CONTINUE,
                 new CalculateRequestBuilder().setSource(source).calculate());
@@ -951,7 +951,7 @@
         final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
                 WINDOWING_MODE_FULLSCREEN);
         final ActivityRecord source = createSourceActivity(fullscreenDisplay);
-        source.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        source.getTask().setWindowingMode(WINDOWING_MODE_FREEFORM);
 
         final ActivityOptions options = ActivityOptions.makeBasic();
         final Rect expected = new Rect(0, 0, 150, 150);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
index 338555e..7d2e9bf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
@@ -30,6 +30,7 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
 
 import static org.junit.Assert.assertEquals;
 
@@ -330,6 +331,23 @@
     }
 
     @Test
+    public void layoutExtendedToDisplayCutout() {
+        addDisplayCutout();
+        final int height = DISPLAY_HEIGHT / 2;
+        mRequestedHeight = UNSPECIFIED_LENGTH;
+        mAttrs.height = height;
+        mAttrs.gravity = Gravity.TOP;
+        mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        mAttrs.setFitInsetsTypes(0);
+        mAttrs.privateFlags |= PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
+        computeFrames();
+
+        assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayFrame);
+        assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mParentFrame);
+        assertRect(0, 0, DISPLAY_WIDTH, height + DISPLAY_CUTOUT_HEIGHT, mFrame);
+    }
+
+    @Test
     public void layoutInDisplayCutoutModeDefaultWithInvisibleSystemBars() {
         addDisplayCutout();
         mState.getSource(ITYPE_STATUS_BAR).setVisible(false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 0f223ca..eea3f84 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -293,7 +293,8 @@
     public void testAssignWindowLayers_ForImeWithAppTargetAndAppAbove() {
         final WindowState appBelowImeTarget = createWindow("appBelowImeTarget");
         final WindowState imeAppTarget = createWindow("imeAppTarget");
-        final WindowState appAboveImeTarget = createWindow("appAboveImeTarget");
+        final WindowState appAboveImeTarget = createWindow(imeAppTarget, TYPE_APPLICATION,
+                "appAboveImeTarget");
 
         mDisplayContent.setImeLayeringTarget(imeAppTarget);
         mDisplayContent.setImeControlTarget(imeAppTarget);
diff --git a/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationManagerService.java b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationManagerService.java
index 0d0b3e0..076059f 100644
--- a/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationManagerService.java
+++ b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationManagerService.java
@@ -108,7 +108,7 @@
         @Override
         public void generateCinematicEffect(@NonNull CinematicEffectRequest request,
                 @NonNull ICinematicEffectListener listener) {
-            if (!runForUserLocked("generateCinematicEffect", (service) ->
+            if (!runForUser("generateCinematicEffect", true, (service) ->
                     service.onGenerateCinematicEffectLocked(request, listener))) {
                 try {
                     listener.onCinematicEffectGenerated(
@@ -126,7 +126,7 @@
 
         @Override
         public void returnCinematicEffectResponse(@NonNull CinematicEffectResponse response) {
-            runForUserLocked("returnCinematicResponse", (service) ->
+            runForUser("returnCinematicResponse", false, (service) ->
                     service.onReturnCinematicEffectResponseLocked(response));
         }
 
@@ -140,30 +140,42 @@
         }
 
         /**
-         * Execute the operation for the user locked. Return true if
-         * WallpaperEffectsGenerationPerUserService is found for the user.
-         * Otherwise return false.
+         * Execute the operation for the user.
+         *
+         * @param func The name of function for logging purpose.
+         * @param checkManageWallpaperEffectsPermission whether to check if caller has
+         *    MANAGE_WALLPAPER_EFFECTS_GENERATION.
+         *    If false, check the uid of caller matching bind service.
+         * @param c WallpaperEffectsGenerationPerUserService operation.
+         * @return whether WallpaperEffectsGenerationPerUserService is found.
          */
-        private boolean runForUserLocked(@NonNull final String func,
+        private boolean runForUser(@NonNull final String func,
+                @NonNull final boolean checkManageWallpaperEffectsPermission,
                 @NonNull final Consumer<WallpaperEffectsGenerationPerUserService> c) {
             ActivityManagerInternal am = LocalServices.getService(ActivityManagerInternal.class);
             final int userId = am.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
                     Binder.getCallingUserHandle().getIdentifier(), false, ALLOW_NON_FULL,
                     null, null);
             if (DEBUG) {
-                Slog.d(TAG, "runForUserLocked:" + func + " from pid=" + Binder.getCallingPid()
+                Slog.d(TAG, "runForUser:" + func + " from pid=" + Binder.getCallingPid()
                         + ", uid=" + Binder.getCallingUid());
             }
-            Context ctx = getContext();
-            if (!(ctx.checkCallingPermission(MANAGE_WALLPAPER_EFFECTS_GENERATION)
-                    == PERMISSION_GRANTED
-                    || mServiceNameResolver.isTemporary(userId)
-                    || mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()))) {
-                String msg = "Permission Denial: Cannot call " + func + " from pid="
-                        + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid();
-                Slog.w(TAG, msg);
-                throw new SecurityException(msg);
+            if (checkManageWallpaperEffectsPermission) {
+                // MANAGE_WALLPAPER_EFFECTS_GENERATION is required for all functions except for
+                // "returnCinematicResponse", whose calling permission checked in
+                // WallpaperEffectsGenerationPerUserService against remote binding.
+                Context ctx = getContext();
+                if (!(ctx.checkCallingPermission(MANAGE_WALLPAPER_EFFECTS_GENERATION)
+                        == PERMISSION_GRANTED
+                        || mServiceNameResolver.isTemporary(userId)
+                        || mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()))) {
+                    String msg = "Permission Denial: Cannot call " + func + " from pid="
+                            + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid();
+                    Slog.w(TAG, msg);
+                    throw new SecurityException(msg);
+                }
             }
+            final int origCallingUid = Binder.getCallingUid();
             final long origId = Binder.clearCallingIdentity();
             boolean accepted = false;
             try {
@@ -171,6 +183,16 @@
                     final WallpaperEffectsGenerationPerUserService service =
                             getServiceForUserLocked(userId);
                     if (service != null) {
+                        // Check uid of caller matches bind service implementation if
+                        // MANAGE_WALLPAPER_EFFECTS_GENERATION is skipped. This is useful
+                        // for service implementation to return response.
+                        if (!checkManageWallpaperEffectsPermission
+                                && !service.isCallingUidAllowed(origCallingUid)) {
+                            String msg = "Permission Denial: cannot call " + func + ", uid["
+                                    + origCallingUid + "] doesn't match service implementation";
+                            Slog.w(TAG, msg);
+                            throw new SecurityException(msg);
+                        }
                         accepted = true;
                         c.accept(service);
                     }
diff --git a/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationPerUserService.java b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationPerUserService.java
index d541051..7b56555 100644
--- a/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationPerUserService.java
+++ b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationPerUserService.java
@@ -141,18 +141,16 @@
         invokeCinematicListenerAndCleanup(cinematicEffectResponse);
     }
 
+    /**
+     * Checks whether the calling uid matches the bind service uid.
+     */
+    public boolean isCallingUidAllowed(int callingUid) {
+        return getServiceUidLocked() ==  callingUid;
+    }
+
     @GuardedBy("mLock")
     private void updateRemoteServiceLocked() {
-        if (mRemoteService != null) {
-            mRemoteService.destroy();
-            mRemoteService = null;
-        }
-        // End existing response and clean up listener for next request.
-        if (mCinematicEffectListenerWrapper != null) {
-            invokeCinematicListenerAndCleanup(
-                    createErrorCinematicEffectResponse(mCinematicEffectListenerWrapper.mTaskId));
-        }
-
+        destroyAndRebindRemoteService();
     }
 
     void onPackageUpdatedLocked() {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ad04952..e395bbe 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -4189,7 +4189,8 @@
      * {@link UiccSlotMapping} which consist of both physical slot index and port index.
      * Logical slot is the slot that is seen by modem. Physical slot is the actual physical slot.
      * Port index is the index (enumerated value) for the associated port available on the SIM.
-     * Each physical slot can have multiple ports if multi-enabled profile(MEP) is supported.
+     * Each physical slot can have multiple ports if
+     * {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP} is supported.
      *
      * Example: no. of logical slots 1 and physical slots 2 do not support MEP, each physical slot
      * has one port:
@@ -4285,11 +4286,11 @@
     /**
      * Get the mapping from logical slots to physical sim slots and port indexes. Initially the
      * logical slot index was mapped to physical slot index, but with support for multi-enabled
-     * profile(MEP) logical slot is now mapped to port index.
+     * profile(MEP){@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP},logical slot is now mapped to
+     * port index.
      *
      * @return a collection of {@link UiccSlotMapping} which indicates the mapping from logical
      *         slots to ports and physical slots.
-     *
      * @hide
      */
     @SystemApi
diff --git a/telephony/java/android/telephony/UiccCardInfo.java b/telephony/java/android/telephony/UiccCardInfo.java
index 30ca162..3843a62 100644
--- a/telephony/java/android/telephony/UiccCardInfo.java
+++ b/telephony/java/android/telephony/UiccCardInfo.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.pm.PackageManager;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -147,9 +148,10 @@
      * Note that this field may be omitted if the caller does not have the correct permissions
      * (see {@link TelephonyManager#getUiccCardsInfo()}).
      *
-     * @deprecated with support for MEP(multiple enabled profile), a SIM card can have more than one
-     * ICCID active at the same time.Instead use {@link UiccPortInfo#getIccId()} to retrieve ICCID.
-     * To find {@link UiccPortInfo} use {@link UiccCardInfo#getPorts()}
+     * @deprecated with support for MEP(multiple enabled profile)
+     * {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP}, a SIM card can have more than one
+     * ICCID active at the same time. Instead use {@link UiccPortInfo#getIccId()} to retrieve ICCID.
+     * To find {@link UiccPortInfo} use {@link UiccCardInfo#getPorts()}.
      *
      * @throws UnsupportedOperationException if the calling app's target SDK is T and beyond.
      */
@@ -192,11 +194,11 @@
     }
 
     /*
-     * Whether the UICC card supports multiple enable profile(MEP)
+     * Whether the UICC card supports multiple enabled profile(MEP)
      * UICCs are generally MEP disabled, there can be only one active profile on the physical
      * sim card.
      *
-     * @return {@code true} if the eUICC is supporting multiple enabled profile(MEP).
+     * @return {@code true} if the UICC is supporting multiple enabled profile(MEP).
      */
     public boolean isMultipleEnabledProfilesSupported() {
         return mIsMultipleEnabledProfilesSupported;
@@ -205,6 +207,9 @@
     /**
      * Get information regarding port, ICCID and its active status.
      *
+     * For device which support {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP}, it should return
+     * more than one {@link UiccPortInfo} object if the card is eUICC.
+     *
      * @return Collection of {@link UiccPortInfo}
      */
     public @NonNull Collection<UiccPortInfo> getPorts() {
diff --git a/telephony/java/android/telephony/UiccPortInfo.java b/telephony/java/android/telephony/UiccPortInfo.java
index d1838c0..6fb0470 100644
--- a/telephony/java/android/telephony/UiccPortInfo.java
+++ b/telephony/java/android/telephony/UiccPortInfo.java
@@ -29,7 +29,9 @@
  * Per GSMA SGP.22 V3.0, a port is a logical entity to which an active UICC profile can be bound on
  * a UICC card. If UICC supports 2 ports, then the port index is numbered 0,1.
  * Each port index is unique within an UICC, but not necessarily unique across UICC’s.
- * For UICC's does not support MEP(Multi-enabled profile), just return the default port index 0.
+ * For UICC's does not support MEP(Multi-enabled profile)
+ * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY_EUICC_MEP}, just return the default
+ * port index 0.
  */
 public final class UiccPortInfo implements Parcelable{
     private final String mIccId;
diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java
index 17f34db..17ce450 100644
--- a/telephony/java/android/telephony/UiccSlotInfo.java
+++ b/telephony/java/android/telephony/UiccSlotInfo.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.content.pm.PackageManager;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -225,6 +226,9 @@
     /**
      * Get Information regarding port, iccid and its active status.
      *
+     * For device which support {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP}, it should return
+     * more than one {@link UiccPortInfo} object if the card is eUICC.
+     *
      * @return Collection of {@link UiccPortInfo}
      */
     public @NonNull Collection<UiccPortInfo> getPorts() {
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index b6ae530..4820d33 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -37,6 +37,7 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.TelephonyManager;
+import android.telephony.UiccCardInfo;
 import android.telephony.euicc.EuiccCardManager.ResetOption;
 import android.util.Log;
 
@@ -931,6 +932,21 @@
      * intent to prompt the user to accept the download. The caller should also be authorized to
      * manage the subscription to be downloaded.
      *
+     * <p>If device support {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP} and
+     * switchAfterDownload is {@code true}, the subscription will be enabled on an esim port based
+     * on the following selection rules:
+     * <ul>
+     *    <li>In SS(Single SIM) mode, if the embedded slot already has an active port, then download
+     *    and enable the subscription on this port.
+     *    <li>In SS mode, if the embedded slot is not active, then try to download and enable the
+     *    subscription on the default port 0 of eUICC.
+     *    <li>In DSDS mode, find first available port to download and enable the subscription.
+     *    (see {@link #isSimPortAvailable(int)})
+     *</ul>
+     * If there is no available port, an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR}
+     * will be returned in the callback intent to prompt the user to disable an already-active
+     * subscription.
+     *
      * @param subscription the subscription to download.
      * @param switchAfterDownload if true, the profile will be activated upon successful download.
      * @param callbackIntent a PendingIntent to launch when the operation completes.
@@ -1141,14 +1157,25 @@
      * intent to prompt the user to accept the download. The caller should also be authorized to
      * manage the subscription to be enabled.
      *
-     * <p> From Android T, devices might support MEP(Multiple Enabled Profiles), the subscription
-     * can be installed on different port from the eUICC. Calling apps with carrier privilege
-     * (see {@link TelephonyManager#hasCarrierPrivileges}) over the currently active subscriptions
-     * can use {@link #switchToSubscription(int, int, PendingIntent)} to specify which port to
-     * enable the subscription. Otherwise, use this API to enable the subscription on the eUICC
-     * and the platform will internally resolve a port. If there is no available port,
-     * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} might be returned in the callback
-     * intent to prompt the user to disable an already-active subscription.
+     * <p> From Android T, devices might support {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP},
+     * the subscription can be installed on different port from the eUICC. Calling apps with
+     * carrier privilege (see {@link TelephonyManager#hasCarrierPrivileges}) over the currently
+     * active subscriptions can use {@link #switchToSubscription(int, int, PendingIntent)} to
+     * specify which port to enable the subscription. Otherwise, use this API to enable the
+     * subscription on the eUICC and the platform will internally resolve a port based on following
+     * rules:
+     * <ul>
+     *    <li>always use the default port 0 is eUICC does not support MEP.
+     *    <li>In SS(Single SIM) mode, if the embedded slot already has an active port, then enable
+     *    the subscription on this port.
+     *    <li>In SS mode, if the embedded slot is not active, then try to enable the subscription on
+     *    the default port 0 of eUICC.
+     *    <li>In DSDS mode, find first available port to enable the subscription.
+     *    (see {@link #isSimPortAvailable(int)})
+     *</ul>
+     * If there is no available port, an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR}
+     * will be returned in the callback intent to prompt the user to disable an already-active
+     * subscription.
      *
      * @param subscriptionId the ID of the subscription to enable. May be
      *     {@link android.telephony.SubscriptionManager#INVALID_SUBSCRIPTION_ID} to deactivate the
@@ -1197,7 +1224,15 @@
      *
      * <p> If the caller is passing invalid port index,
      * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_ERROR} with detailed error code
-     * {@link #ERROR_INVALID_PORT} will be returned.
+     * {@link #ERROR_INVALID_PORT} will be returned. The port index is invalid if one of the
+     * following requirements is met:
+     * <ul>
+     *     <li>index is beyond the range of {@link UiccCardInfo#getPorts()}.
+     *     <li>In SS(Single SIM) mode, the embedded slot already has an active port with different
+     *     port index.
+     *     <li>In DSDS mode, if the psim slot is active and the embedded slot already has an active
+     *     empty port with different port index.
+     * </ul>
      *
      * <p> Depending on the target port and permission check,
      * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} might be returned to the callback
@@ -1522,8 +1557,8 @@
 
     /**
      * Returns whether the passing portIndex is available.
-     * A port is available if it has no profiles enabled on it or calling app has carrier privilege
-     * over the profile installed on the selected port.
+     * A port is available if it is active without enabled profile on it or
+     * calling app has carrier privilege over the profile installed on the selected port.
      * Always returns false if the cardId is a physical card.
      *
      * @param portIndex is an enumeration of the ports available on the UICC.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
index 8d60466..4cddd85 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
@@ -60,7 +60,7 @@
         }
         teardown {
             test {
-                testApp.exit()
+                testApp.exit(wmHelper)
             }
         }
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
index 7ee6451..5bd365c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
@@ -64,7 +64,6 @@
             device.waitForIdle()
         } else {
             wmHelper.waitImeShown()
-            wmHelper.waitForAppTransitionIdle()
         }
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
index b66c45c7..a135e0a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
@@ -53,8 +53,8 @@
         button.click()
 
         device.wait(Until.gone(launchActivityButton), FIND_TIMEOUT)
-        wmHelper.waitForFullScreenApp(secondActivityComponent)
         wmHelper.waitFor(
+            WindowManagerStateHelper.isAppFullScreen(secondActivityComponent),
             WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY),
             WindowManagerConditionsFactory.hasLayersAnimating().negate()
         )
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index ba5698c..a9564fd 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -88,7 +88,7 @@
             }
             transitions {
                 device.reopenAppFromOverview(wmHelper)
-                require(wmHelper.waitImeShown()) { "IME didn't show in time" }
+                wmHelper.waitImeShown()
             }
             teardown {
                 test {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
index 19e2c92..7e3ed82 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
@@ -18,6 +18,7 @@
 
 import android.app.Instrumentation
 import android.platform.test.annotations.Presubmit
+import android.view.Display
 import android.view.Surface
 import android.view.WindowManagerPolicyConstants
 import androidx.test.filters.RequiresDevice
@@ -35,6 +36,8 @@
 import com.android.server.wm.flicker.navBarWindowIsVisible
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -64,12 +67,22 @@
                 eachRun {
                     this.setRotation(testSpec.startRotation)
                     testApp.launchViaIntent(wmHelper)
-                    wmHelper.waitForFullScreenApp(testApp.component)
-                    wmHelper.waitForAppTransitionIdle()
+                    val testAppVisible = wmHelper.waitFor(
+                        WindowManagerStateHelper.isAppFullScreen(testApp.component),
+                        WindowManagerConditionsFactory.isAppTransitionIdle(
+                            Display.DEFAULT_DISPLAY))
+                    require(testAppVisible) {
+                        "Expected ${testApp.component.toWindowName()} to be visible"
+                    }
 
                     imeTestApp.launchViaIntent(wmHelper)
-                    wmHelper.waitForFullScreenApp(testApp.component)
-                    wmHelper.waitForAppTransitionIdle()
+                    val imeAppVisible = wmHelper.waitFor(
+                        WindowManagerStateHelper.isAppFullScreen(imeTestApp.component),
+                        WindowManagerConditionsFactory.isAppTransitionIdle(
+                            Display.DEFAULT_DISPLAY))
+                    require(imeAppVisible) {
+                        "Expected ${imeTestApp.component.toWindowName()} to be visible"
+                    }
 
                     imeTestApp.openIME(device, wmHelper)
                 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
index b5e13be..cc808a0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
@@ -18,6 +18,7 @@
 
 import android.app.Instrumentation
 import android.platform.test.annotations.Presubmit
+import android.view.Display
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.entireScreenCovered
@@ -30,7 +31,9 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.TwoActivitiesAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory
 import com.android.server.wm.traces.parser.toFlickerComponent
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -77,14 +80,16 @@
             }
             teardown {
                 test {
-                    testApp.exit()
+                    testApp.exit(wmHelper)
                 }
             }
             transitions {
                 testApp.openSecondActivity(device, wmHelper)
                 device.pressBack()
-                wmHelper.waitForAppTransitionIdle()
-                wmHelper.waitForFullScreenApp(testApp.component)
+                val firstActivityVisible = wmHelper.waitFor(
+                    WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY),
+                    WindowManagerStateHelper.isAppFullScreen(testApp.component))
+                require(firstActivityVisible) { "Expected ${testApp.component} to be visible" }
             }
         }
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
index 53560cc..4313b8d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
@@ -56,7 +56,7 @@
         }
         teardown {
             test {
-                testApp.exit()
+                testApp.exit(wmHelper)
             }
         }
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index f21b1d6..74526bb 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -23,6 +23,7 @@
 import android.view.Surface
 import android.view.WindowManagerPolicyConstants
 import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.server.wm.flicker.FlickerBuilderProvider
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
@@ -66,6 +67,7 @@
 @Group1
 open class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParameter) {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val taplInstrumentation = LauncherInstrumentation()
 
     private val testApp1 = SimpleAppHelper(instrumentation)
     private val testApp2 = NonResizeableAppHelper(instrumentation)
@@ -81,6 +83,10 @@
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
             setup {
+                test {
+                    taplInstrumentation.setExpectedRotation(testSpec.startRotation)
+                }
+
                 eachRun {
                     testApp1.launchViaIntent(wmHelper)
                     wmHelper.waitForFullScreenApp(testApp1.component)
@@ -90,20 +96,10 @@
                 }
             }
             transitions {
-                // Swipe right from bottom to quick switch back
-                // NOTE: We don't perform an edge-to-edge swipe but instead only swipe in the middle
-                // as to not accidentally trigger a swipe back or forward action which would result
-                // in the same behavior but not testing quick swap.
-                device.swipe(
-                        startDisplayBounds.bounds.right / 3,
-                        startDisplayBounds.bounds.bottom,
-                        2 * startDisplayBounds.bounds.right / 3,
-                        startDisplayBounds.bounds.bottom,
-                        if (testSpec.isLandscapeOrSeascapeAtStart) 75 else 30
-                )
-
+                taplInstrumentation.launchedAppState.quickSwitchToPreviousApp()
                 wmHelper.waitForFullScreenApp(testApp1.component)
                 wmHelper.waitForAppTransitionIdle()
+                wmHelper.waitForNavBarStatusBarVisible()
             }
 
             teardown {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index ce6a383..5d172e2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -22,6 +22,7 @@
 import android.view.Surface
 import android.view.WindowManagerPolicyConstants
 import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.server.wm.flicker.FlickerBuilderProvider
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
@@ -65,6 +66,7 @@
 @Group1
 open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestParameter) {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val taplInstrumentation = LauncherInstrumentation()
 
     private val testApp1 = SimpleAppHelper(instrumentation)
     private val testApp2 = NonResizeableAppHelper(instrumentation)
@@ -73,6 +75,10 @@
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
             setup {
+                test {
+                    taplInstrumentation.setExpectedRotation(testSpec.startRotation)
+                }
+
                 eachRun {
                     testApp1.launchViaIntent(wmHelper)
                     wmHelper.waitForFullScreenApp(testApp1.component)
@@ -85,43 +91,24 @@
                         ?.layerStackSpace
                         ?: error("Display not found")
 
-                    // Swipe right from bottom to quick switch back
-                    // NOTE: We don't perform an edge-to-edge swipe but instead only swipe in the
-                    // middle as to not accidentally trigger a swipe back or forward action which
-                    // would result in the same behavior but not testing quick swap.
-                    device.swipe(
-                            startDisplayBounds.right / 3,
-                            startDisplayBounds.bottom,
-                            2 * startDisplayBounds.right / 3,
-                            startDisplayBounds.bottom,
-                            if (testSpec.isLandscapeOrSeascapeAtStart) 75 else 30
-                    )
+                    taplInstrumentation.launchedAppState.quickSwitchToPreviousApp()
 
                     wmHelper.waitForFullScreenApp(testApp1.component)
                     wmHelper.waitForAppTransitionIdle()
                 }
             }
             transitions {
-                // Swipe left from bottom to quick switch forward
-                // NOTE: We don't perform an edge-to-edge swipe but instead only swipe in the middle
-                // as to not accidentally trigger a swipe back or forward action which would result
-                // in the same behavior but not testing quick swap.
-                device.swipe(
-                        2 * startDisplayBounds.right / 3,
-                        startDisplayBounds.bottom,
-                        startDisplayBounds.right / 3,
-                        startDisplayBounds.bottom,
-                        if (testSpec.isLandscapeOrSeascapeAtStart) 75 else 30
-                )
+                taplInstrumentation.launchedAppState.quickSwitchToPreviousAppSwipeLeft()
 
                 wmHelper.waitForFullScreenApp(testApp2.component)
                 wmHelper.waitForAppTransitionIdle()
+                wmHelper.waitForNavBarStatusBarVisible()
             }
 
             teardown {
                 test {
-                    testApp1.exit()
-                    testApp2.exit()
+                    testApp1.exit(wmHelper)
+                    testApp2.exit(wmHelper)
                 }
             }
         }
@@ -365,4 +352,4 @@
                     )
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index 1a762bf..e5e2404 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -22,6 +22,7 @@
 import android.view.Surface
 import android.view.WindowManagerPolicyConstants
 import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.server.wm.flicker.FlickerBuilderProvider
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
@@ -62,13 +63,20 @@
 @Group4
 class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val taplInstrumentation = LauncherInstrumentation()
+
     private val testApp = SimpleAppHelper(instrumentation)
+
     private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
 
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
             setup {
+                test {
+                    taplInstrumentation.setExpectedRotation(testSpec.startRotation)
+                }
+
                 eachRun {
                     testApp.launchViaIntent(wmHelper)
                     device.pressHome()
@@ -77,20 +85,10 @@
                 }
             }
             transitions {
-                // Swipe right from bottom to quick switch back
-                // NOTE: We don't perform an edge-to-edge swipe but instead only swipe in the middle
-                // as to not accidentally trigger a swipe back or forward action which would result
-                // in the same behavior but not testing quick swap.
-                device.swipe(
-                        startDisplayBounds.bounds.right / 3,
-                        startDisplayBounds.bounds.bottom,
-                        2 * startDisplayBounds.bounds.right / 3,
-                        startDisplayBounds.bounds.bottom,
-                        50
-                )
-
+                taplInstrumentation.workspace.quickSwitchToPreviousApp()
                 wmHelper.waitForFullScreenApp(testApp.component)
                 wmHelper.waitForAppTransitionIdle()
+                wmHelper.waitForNavBarStatusBarVisible()
             }
 
             teardown {
diff --git a/tests/InputMethodStressTest/AndroidTest.xml b/tests/InputMethodStressTest/AndroidTest.xml
index b194010..fc54ca6 100644
--- a/tests/InputMethodStressTest/AndroidTest.xml
+++ b/tests/InputMethodStressTest/AndroidTest.xml
@@ -18,6 +18,11 @@
 
     <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
 
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="adb shell settings put secure show_ime_with_hard_keyboard 1" />
+        <option name="teardown-command" value="adb shell settings delete secure show_ime_with_hard_keyboard" />
+    </target_preparer>
+
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="InputMethodStressTest.apk" />
diff --git a/tools/bit/command.cpp b/tools/bit/command.cpp
index f95ea11..6c68e0b 100644
--- a/tools/bit/command.cpp
+++ b/tools/bit/command.cpp
@@ -192,10 +192,11 @@
     if (strchr(prog, '/') != NULL) {
         return execve(prog, (char*const*)argv, (char*const*)envp);
     } else {
-        char* pathEnv = strdup(getenv("PATH"));
-        if (pathEnv == NULL) {
+        const char* pathEnvRaw = getenv("PATH");
+        if (pathEnvRaw == NULL) {
             return 1;
         }
+        char* pathEnv = strdup(pathEnvRaw);
         char* dir = pathEnv;
         while (dir) {
             char* next = strchr(dir, ':');
diff --git a/tools/lint/README.md b/tools/lint/README.md
index b534b62..c674d36 100644
--- a/tools/lint/README.md
+++ b/tools/lint/README.md
@@ -78,6 +78,7 @@
 ## Documentation
 
 - [Android Lint Docs](https://googlesamples.github.io/android-custom-lint-rules/)
+- [Lint Check Unit Testing](https://googlesamples.github.io/android-custom-lint-rules/api-guide/unit-testing.md.html)
 - [Android Lint source files](https://source.corp.google.com/studio-main/tools/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/)
 - [PSI source files](https://github.com/JetBrains/intellij-community/tree/master/java/java-psi-api/src/com/intellij/psi)
 - [UAST source files](https://upsource.jetbrains.com/idea-ce/structure/idea-ce-7b9b8cc138bbd90aec26433f82cd2c6838694003/uast/uast-common/src/org/jetbrains/uast)
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
index a6fd9bb..859961a 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
@@ -19,6 +19,7 @@
 import com.android.tools.lint.client.api.IssueRegistry
 import com.android.tools.lint.client.api.Vendor
 import com.android.tools.lint.detector.api.CURRENT_API
+import com.google.android.lint.parcel.SaferParcelChecker
 import com.google.auto.service.AutoService
 
 @AutoService(IssueRegistry::class)
@@ -33,7 +34,8 @@
             CallingIdentityTokenDetector.ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY,
             CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED,
             EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
-            EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION
+            EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION,
+            SaferParcelChecker.ISSUE_UNSAFE_API_USAGE,
     )
 
     override val api: Int
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt
new file mode 100644
index 0000000..cc2ab19
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.lint.parcel
+
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.LintFix
+import com.android.tools.lint.detector.api.Location
+import com.intellij.psi.PsiCallExpression
+import com.intellij.psi.PsiClassType
+import com.intellij.psi.PsiIntersectionType
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiType
+import com.intellij.psi.PsiTypeParameter
+import com.intellij.psi.PsiWildcardType
+import org.jetbrains.kotlin.utils.addToStdlib.cast
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UExpression
+import org.jetbrains.uast.UVariable
+
+/**
+ * Subclass this class and override {@link #getBoundingClass} to report an unsafe Parcel API issue
+ * with a fix that migrates towards the new safer API by appending an argument in the form of
+ * {@code com.package.ItemType.class} coming from the result of the overridden method.
+ */
+abstract class CallMigrator(
+        val method: Method,
+        private val rejects: Set<String> = emptySet(),
+) {
+    open fun report(context: JavaContext, call: UCallExpression, method: PsiMethod) {
+        val location = context.getLocation(call)
+        val itemType = getBoundingClass(context, call, method)
+        val fix = (itemType as? PsiClassType)?.let { type ->
+            getParcelFix(location, this.method.name, getArgumentSuffix(type))
+        }
+        val message = "Unsafe `Parcel.${this.method.name}()` API usage"
+        context.report(SaferParcelChecker.ISSUE_UNSAFE_API_USAGE, call, location, message, fix)
+    }
+
+    protected open fun getArgumentSuffix(type: PsiClassType) =
+            ", ${type.rawType().canonicalText}.class"
+
+    protected open fun getBoundingClass(
+            context: JavaContext,
+            call: UCallExpression,
+            method: PsiMethod,
+    ): PsiType? = null
+
+    protected fun getItemType(type: PsiType, container: String): PsiClassType? {
+        val supers = getParentTypes(type).mapNotNull { it as? PsiClassType }
+        val containerType = supers.firstOrNull { it.rawType().canonicalText == container }
+                ?: return null
+        val itemType = containerType.parameters.getOrNull(0) ?: return null
+        // TODO: Expand to other types, see PsiTypeVisitor
+        return when (itemType) {
+            is PsiClassType -> itemType
+            is PsiWildcardType -> itemType.bound as PsiClassType
+            else -> null
+        }
+    }
+
+    /**
+     * Tries to obtain the type expected by the "receiving" end given a certain {@link UExpression}.
+     *
+     * This could be an assignment, an argument passed to a method call, to a constructor call, a
+     * type cast, etc. If no receiving end is found, the type of the UExpression itself is returned.
+     */
+    protected fun getReceivingType(expression: UExpression): PsiType? {
+        val parent = expression.uastParent
+        val type = when (parent) {
+            is UCallExpression -> {
+                val i = parent.valueArguments.indexOf(expression)
+                val psiCall = parent.sourcePsi as? PsiCallExpression ?: return null
+                val typeSubstitutor = psiCall.resolveMethodGenerics().substitutor
+                val method = psiCall.resolveMethod()!!
+                method.getSignature(typeSubstitutor).parameterTypes[i]
+            }
+            is UVariable -> parent.type
+            is UExpression -> parent.getExpressionType()
+            else -> null
+        }
+        return filter(type ?: expression.getExpressionType())
+    }
+
+    private fun filter(type: PsiType?): PsiType? {
+        // It's important that PsiIntersectionType case is above the one that check the type in
+        // rejects, because for intersect types, the canonicalText is one of the terms.
+        if (type is PsiIntersectionType) {
+            return type.conjuncts.mapNotNull(this::filter).firstOrNull()
+        }
+        if (type == null || type.canonicalText in rejects) {
+            return null
+        }
+        if (type is PsiClassType && type.resolve() is PsiTypeParameter) {
+            return null
+        }
+        return type
+    }
+
+    private fun getParentTypes(type: PsiType): Set<PsiType> =
+            type.superTypes.flatMap(::getParentTypes).toSet() + type
+
+    protected fun getParcelFix(location: Location, method: String, arguments: String) =
+            LintFix
+                    .create()
+                    .name("Migrate to safer Parcel.$method() API")
+                    .replace()
+                    .range(location)
+                    .pattern("$method\\s*\\(((?:.|\\n)*)\\)")
+                    .with("\\k<1>$arguments")
+                    .autoFix()
+                    .build()
+}
+
+/**
+ * This class derives the type to be appended by inferring the generic type of the {@code container}
+ * type (eg. "java.util.List") of the {@code argument}-th argument.
+ */
+class ContainerArgumentMigrator(
+        method: Method,
+        private val argument: Int,
+        private val container: String,
+        rejects: Set<String> = emptySet(),
+) : CallMigrator(method, rejects) {
+    override fun getBoundingClass(
+            context: JavaContext, call: UCallExpression, method: PsiMethod
+    ): PsiType? {
+        val firstParamType = call.valueArguments[argument].getExpressionType() ?: return null
+        return getItemType(firstParamType, container)!!
+    }
+
+    /**
+     * We need to insert a casting construct in the class parameter. For example:
+     *   (Class<Foo<Bar>>) (Class<?>) Foo.class.
+     * This is needed for when the arguments of the conflict (eg. when there is List<Foo<Bar>> and
+     * class type is Class<Foo?).
+     */
+    override fun getArgumentSuffix(type: PsiClassType): String {
+        if (type.parameters.isNotEmpty()) {
+            val rawType = type.rawType()
+            return ", (Class<${type.canonicalText}>) (Class<?>) ${rawType.canonicalText}.class"
+        }
+        return super.getArgumentSuffix(type)
+    }
+}
+
+/**
+ * This class derives the type to be appended by inferring the generic type of the {@code container}
+ * type (eg. "java.util.List") of the return type of the method.
+ */
+class ContainerReturnMigrator(
+        method: Method,
+        private val container: String,
+        rejects: Set<String> = emptySet(),
+) : CallMigrator(method, rejects) {
+    override fun getBoundingClass(
+            context: JavaContext, call: UCallExpression, method: PsiMethod
+    ): PsiType? {
+        val type = getReceivingType(call.uastParent as UExpression) ?: return null
+        return getItemType(type, container)
+    }
+}
+
+/**
+ * This class derives the type to be appended by inferring the expected type for the method result.
+ */
+class ReturnMigrator(
+        method: Method,
+        rejects: Set<String> = emptySet(),
+) : CallMigrator(method, rejects) {
+    override fun getBoundingClass(
+            context: JavaContext, call: UCallExpression, method: PsiMethod
+    ): PsiType? {
+        return getReceivingType(call.uastParent as UExpression)
+    }
+}
+
+/**
+ * This class appends the class loader and the class object by deriving the type from the method
+ * result.
+ */
+class ReturnMigratorWithClassLoader(
+        method: Method,
+        rejects: Set<String> = emptySet(),
+) : CallMigrator(method, rejects) {
+    override fun getBoundingClass(
+            context: JavaContext, call: UCallExpression, method: PsiMethod
+    ): PsiType? {
+        return getReceivingType(call.uastParent as UExpression)
+    }
+
+    override fun getArgumentSuffix(type: PsiClassType): String =
+            "${type.rawType().canonicalText}.class.getClassLoader(), " +
+                    "${type.rawType().canonicalText}.class"
+
+}
\ No newline at end of file
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/Method.kt b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/Method.kt
new file mode 100644
index 0000000..c032fa2
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/Method.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.lint.parcel
+
+data class Method(
+    val params: List<String>,
+    val clazz: String,
+    val name: String,
+    val parameters: List<String>
+) {
+    constructor(
+        clazz: String,
+        name: String,
+        parameters: List<String>
+    ) : this(
+            listOf(), clazz, name, parameters
+    )
+
+    val signature: String
+        get() {
+            val prefix = if (params.isEmpty()) "" else "${params.joinToString(", ", "<", ">")} "
+            return "$prefix$clazz.$name(${parameters.joinToString()})"
+        }
+}
\ No newline at end of file
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt
new file mode 100644
index 0000000..89dbcae
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.lint.parcel
+
+import com.android.tools.lint.detector.api.*
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiSubstitutor
+import com.intellij.psi.PsiType
+import com.intellij.psi.PsiTypeParameter
+import org.jetbrains.uast.UCallExpression
+import java.util.*
+
+class SaferParcelChecker : Detector(), SourceCodeScanner {
+    override fun getApplicableMethodNames(): List<String> =
+            MIGRATORS
+                    .map(CallMigrator::method)
+                    .map(Method::name)
+
+    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+        if (!isAtLeastT(context)) return
+        val signature = getSignature(method)
+        val migrator = MIGRATORS.firstOrNull { it.method.signature == signature } ?: return
+        migrator.report(context, node, method)
+    }
+
+    private fun getSignature(method: PsiMethod): String {
+        val name = UastLintUtils.getQualifiedName(method)
+        val signature = method.getSignature(PsiSubstitutor.EMPTY)
+        val parameters =
+                signature.parameterTypes.joinToString(transform = PsiType::getCanonicalText)
+        val types = signature.typeParameters.map(PsiTypeParameter::getName)
+        val prefix = if (types.isEmpty()) "" else types.joinToString(", ", "<", ">") + " "
+        return "$prefix$name($parameters)"
+    }
+
+    /** Taken from androidx-main:core/core/src/main/java/androidx/core/os/BuildCompat.java */
+    private fun isAtLeastT(context: Context): Boolean {
+        val project = if (context.isGlobalAnalysis()) context.mainProject else context.project
+        return project.isAndroidProject
+                && project.minSdkVersion.featureLevel >= 32
+                && isAtLeastPreReleaseCodename("Tiramisu", project.minSdkVersion.codename)
+    }
+
+    /** Taken from androidx-main:core/core/src/main/java/androidx/core/os/BuildCompat.java */
+    private fun isAtLeastPreReleaseCodename(min: String, actual: String): Boolean {
+        if (actual == "REL") return false
+        return actual.uppercase(Locale.ROOT) >= min.uppercase(Locale.ROOT)
+    }
+
+    companion object {
+        @JvmField
+        val ISSUE_UNSAFE_API_USAGE: Issue = Issue.create(
+                id = "UnsafeParcelApi",
+                briefDescription = "Use of unsafe Parcel API",
+                explanation = """
+                    You are using a deprecated Parcel API that doesn't accept the expected class as\
+                     a parameter. This means that unexpected classes could be instantiated and\
+                     unexpected code executed.
+
+                    Please migrate to the safer alternative that takes an extra Class<T> parameter.
+                    """,
+                category = Category.SECURITY,
+                priority = 8,
+                severity = Severity.WARNING,
+
+                implementation = Implementation(
+                        SaferParcelChecker::class.java,
+                        Scope.JAVA_FILE_SCOPE
+                )
+        )
+
+        private val METHOD_READ_SERIALIZABLE = Method("android.os.Parcel", "readSerializable", listOf())
+        private val METHOD_READ_ARRAY_LIST = Method("android.os.Parcel", "readArrayList", listOf("java.lang.ClassLoader"))
+        private val METHOD_READ_LIST = Method("android.os.Parcel", "readList", listOf("java.util.List", "java.lang.ClassLoader"))
+        private val METHOD_READ_PARCELABLE = Method(listOf("T"), "android.os.Parcel", "readParcelable", listOf("java.lang.ClassLoader"))
+        private val METHOD_READ_PARCELABLE_LIST = Method(listOf("T"), "android.os.Parcel", "readParcelableList", listOf("java.util.List<T>", "java.lang.ClassLoader"))
+        private val METHOD_READ_SPARSE_ARRAY = Method(listOf("T"), "android.os.Parcel", "readSparseArray", listOf("java.lang.ClassLoader"))
+
+        // TODO: Write migrators for methods below
+        private val METHOD_READ_ARRAY = Method("android.os.Parcel", "readArray", listOf("java.lang.ClassLoader"))
+        private val METHOD_READ_PARCELABLE_ARRAY = Method("android.os.Parcel", "readParcelableArray", listOf("java.lang.ClassLoader"))
+        private val METHOD_READ_PARCELABLE_CREATOR = Method("android.os.Parcel", "readParcelableCreator", listOf("java.lang.ClassLoader"))
+
+        private val MIGRATORS = listOf(
+                ReturnMigrator(METHOD_READ_PARCELABLE, setOf("android.os.Parcelable")),
+                ContainerArgumentMigrator(METHOD_READ_LIST, 0, "java.util.List"),
+                ContainerReturnMigrator(METHOD_READ_ARRAY_LIST, "java.util.Collection"),
+                ContainerReturnMigrator(METHOD_READ_SPARSE_ARRAY, "android.util.SparseArray"),
+                ContainerArgumentMigrator(METHOD_READ_PARCELABLE_LIST, 0, "java.util.List"),
+                ReturnMigratorWithClassLoader(METHOD_READ_SERIALIZABLE),
+        )
+    }
+}
\ No newline at end of file
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt
new file mode 100644
index 0000000..05c7850
--- /dev/null
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.lint.parcel
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.checks.infrastructure.TestMode
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class SaferParcelCheckerTest : LintDetectorTest() {
+    override fun getDetector(): Detector = SaferParcelChecker()
+
+    override fun getIssues(): List<Issue> = listOf(
+            SaferParcelChecker.ISSUE_UNSAFE_API_USAGE
+    )
+
+    override fun lint(): TestLintTask =
+            super.lint()
+                    .allowMissingSdk(true)
+                    // We don't do partial analysis in the platform
+                    .skipTestModes(TestMode.PARTIAL)
+
+    fun testDetectUnsafeReadSerializable() {
+        lint()
+                .files(
+                        java(
+                                """
+                        package test.pkg;
+                        import android.os.Parcel;
+                        import java.io.Serializable;
+
+                        public class TestClass {
+                            private TestClass(Parcel p) {
+                                Serializable ans = p.readSerializable();
+                            }
+                        }
+                        """
+                        ).indented(),
+                        *includes
+                )
+                .expectIdenticalTestModeOutput(false)
+                .run()
+                .expect(
+                        """
+                        src/test/pkg/TestClass.java:7: Warning: Unsafe Parcel.readSerializable() \
+                        API usage [UnsafeParcelApi]
+                                Serializable ans = p.readSerializable();
+                                                   ~~~~~~~~~~~~~~~~~~~~
+                        0 errors, 1 warnings
+                        """.addLineContinuation()
+                )
+    }
+
+    fun testDoesNotDetectSafeReadSerializable() {
+        lint()
+                .files(
+                        java(
+                                """
+                        package test.pkg;
+                        import android.os.Parcel;
+                        import java.io.Serializable;
+
+                        public class TestClass {
+                            private TestClass(Parcel p) {
+                                String ans = p.readSerializable(null, String.class);
+                            }
+                        }
+                        """
+                        ).indented(),
+                        *includes
+                )
+                .run()
+                .expect("No warnings.")
+    }
+
+    fun testDetectUnsafeReadArrayList() {
+        lint()
+                .files(
+                        java(
+                                """
+                        package test.pkg;
+                        import android.os.Parcel;
+
+                        public class TestClass {
+                            private TestClass(Parcel p) {
+                                ArrayList ans = p.readArrayList(null);
+                            }
+                        }
+                        """
+                        ).indented(),
+                        *includes
+                )
+                .run()
+                .expect(
+                        """
+                        src/test/pkg/TestClass.java:6: Warning: Unsafe Parcel.readArrayList() API \
+                        usage [UnsafeParcelApi]
+                                ArrayList ans = p.readArrayList(null);
+                                                ~~~~~~~~~~~~~~~~~~~~~
+                        0 errors, 1 warnings
+                        """.addLineContinuation()
+                )
+    }
+
+    fun testDoesNotDetectSafeReadArrayList() {
+        lint()
+                .files(
+                        java(
+                                """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        ArrayList<Intent> ans = p.readArrayList(null, Intent.class);
+                                    }
+                                }
+                                """
+                        ).indented(),
+                        *includes
+                )
+                .run()
+                .expect("No warnings.")
+    }
+
+    fun testDetectUnsafeReadList() {
+        lint()
+                .files(
+                        java(
+                                """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+                                import java.util.List;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        List<Intent> list = new ArrayList<Intent>();
+                                        p.readList(list, null);
+                                    }
+                                }
+                                """
+                        ).indented(),
+                        *includes
+                )
+                .run()
+                .expect(
+                        """
+                        src/test/pkg/TestClass.java:9: Warning: Unsafe Parcel.readList() API usage \
+                        [UnsafeParcelApi]
+                                p.readList(list, null);
+                                ~~~~~~~~~~~~~~~~~~~~~~
+                        0 errors, 1 warnings
+                        """.addLineContinuation()
+                )
+    }
+
+    fun testDoesNotDetectSafeReadList() {
+        lint()
+                .files(
+                        java(
+                                """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+                                import java.util.List;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        List<Intent> list = new ArrayList<Intent>();
+                                        p.readList(list, null, Intent.class);
+                                    }
+                                }
+                                """
+                        ).indented(),
+                        *includes
+                )
+                .run()
+                .expect("No warnings.")
+    }
+
+    fun testDetectUnsafeReadParcelable() {
+        lint()
+                .files(
+                        java(
+                                """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        Intent ans = p.readParcelable(null);
+                                    }
+                                }
+                                """
+                        ).indented(),
+                        *includes
+                )
+                .run()
+                .expect(
+                        """
+                        src/test/pkg/TestClass.java:7: Warning: Unsafe Parcel.readParcelable() API \
+                        usage [UnsafeParcelApi]
+                                Intent ans = p.readParcelable(null);
+                                             ~~~~~~~~~~~~~~~~~~~~~~
+                        0 errors, 1 warnings
+                        """.addLineContinuation()
+                )
+    }
+
+    fun testDoesNotDetectSafeReadParcelable() {
+        lint()
+                .files(
+                        java(
+                                """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        Intent ans = p.readParcelable(null, Intent.class);
+                                    }
+                                }
+                                """
+                        ).indented(),
+                        *includes
+                )
+                .run()
+                .expect("No warnings.")
+    }
+
+    fun testDetectUnsafeReadParcelableList() {
+        lint()
+                .files(
+                        java(
+                                """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+                                import java.util.List;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        List<Intent> list = new ArrayList<Intent>();
+                                        List<Intent> ans = p.readParcelableList(list, null);
+                                    }
+                                }
+                                """
+                        ).indented(),
+                        *includes
+                )
+                .run()
+                .expect(
+                        """
+                        src/test/pkg/TestClass.java:9: Warning: Unsafe Parcel.readParcelableList() \
+                        API usage [UnsafeParcelApi]
+                                List<Intent> ans = p.readParcelableList(list, null);
+                                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        0 errors, 1 warnings
+                        """.addLineContinuation()
+                )
+    }
+
+    fun testDoesNotDetectSafeReadParcelableList() {
+        lint()
+                .files(
+                        java(
+                                """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+                                import java.util.List;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        List<Intent> list = new ArrayList<Intent>();
+                                        List<Intent> ans =
+                                                p.readParcelableList(list, null, Intent.class);
+                                    }
+                                }
+                                """
+                        ).indented(),
+                        *includes
+                )
+                .run()
+                .expect("No warnings.")
+    }
+
+    fun testDetectUnsafeReadSparseArray() {
+        lint()
+                .files(
+                        java(
+                                """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+                                import android.util.SparseArray;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        SparseArray<Intent> ans = p.readSparseArray(null);
+                                    }
+                                }
+                                """
+                        ).indented(),
+                        *includes
+                )
+                .run()
+                .expect(
+                        """
+                        src/test/pkg/TestClass.java:8: Warning: Unsafe Parcel.readSparseArray() API\
+                         usage [UnsafeParcelApi]
+                                SparseArray<Intent> ans = p.readSparseArray(null);
+                                                          ~~~~~~~~~~~~~~~~~~~~~~~
+                        0 errors, 1 warnings
+                        """.addLineContinuation()
+                )
+    }
+
+    fun testDoesNotDetectSafeReadSparseArray() {
+        lint()
+                .files(
+                        java(
+                                """
+                                package test.pkg;
+                                import android.content.Intent;
+                                import android.os.Parcel;
+                                import android.util.SparseArray;
+
+                                public class TestClass {
+                                    private TestClass(Parcel p) {
+                                        SparseArray<Intent> ans =
+                                                p.readSparseArray(null, Intent.class);
+                                    }
+                                }
+                                """
+                        ).indented(),
+                        *includes
+                )
+                .run()
+                .expect("No warnings.")
+    }
+
+    /** Stubs for classes used for testing */
+
+
+    private val includes =
+            arrayOf(
+                manifest().minSdk("Tiramisu"),
+                java(
+                        """
+                        package android.os;
+                        import java.util.ArrayList;
+                        import java.util.List;
+                        import java.util.Map;
+                        import java.util.HashMap;
+
+                        public final class Parcel {
+                            // Deprecateds
+                            public Object[] readArray(ClassLoader loader) { return null; }
+                            public ArrayList readArrayList(ClassLoader loader) { return null; }
+                            public HashMap readHashMap(ClassLoader loader) { return null; }
+                            public void readList(List outVal, ClassLoader loader) {}
+                            public void readMap(Map outVal, ClassLoader loader) {}
+                            public <T extends Parcelable> T readParcelable(ClassLoader loader) { return null; }
+                            public Parcelable[] readParcelableArray(ClassLoader loader) { return null; }
+                            public Parcelable.Creator<?> readParcelableCreator(ClassLoader loader) { return null; }
+                            public <T extends Parcelable> List<T> readParcelableList(List<T> list, ClassLoader cl) { return null; }
+                            public Serializable readSerializable() { return null; }
+                            public <T> SparseArray<T> readSparseArray(ClassLoader loader) { return null; }
+
+                            // Replacements
+                            public <T> T[] readArray(ClassLoader loader, Class<T> clazz) { return null; }
+                            public <T> ArrayList<T> readArrayList(ClassLoader loader, Class<? extends T> clazz) { return null; }
+                            public <K, V> HashMap<K,V> readHashMap(ClassLoader loader, Class<? extends K> clazzKey, Class<? extends V> clazzValue) { return null; }
+                            public <T> void readList(List<? super T> outVal, ClassLoader loader, Class<T> clazz) {}
+                            public <K, V> void readMap(Map<? super K, ? super V> outVal, ClassLoader loader, Class<K> clazzKey, Class<V> clazzValue) {}
+                            public <T> T readParcelable(ClassLoader loader, Class<T> clazz) { return null; }
+                            public <T> T[] readParcelableArray(ClassLoader loader, Class<T> clazz) { return null; }
+                            public <T> Parcelable.Creator<T> readParcelableCreator(ClassLoader loader, Class<T> clazz) { return null; }
+                            public <T> List<T> readParcelableList(List<T> list, ClassLoader cl, Class<T> clazz) { return null; }
+                            public <T> T readSerializable(ClassLoader loader, Class<T> clazz) { return null; }
+                            public <T> SparseArray<T> readSparseArray(ClassLoader loader, Class<? extends T> clazz) { return null; }
+                        }
+                        """
+                ).indented(),
+                java(
+                        """
+                        package android.os;
+                        public interface Parcelable {}
+                        """
+                ).indented(),
+                java(
+                        """
+                        package android.content;
+                        public class Intent implements Parcelable, Cloneable {}
+                        """
+                ).indented(),
+                java(
+                        """
+                        package android.util;
+                        public class SparseArray<E> implements Cloneable {}
+                        """
+                ).indented(),
+            )
+
+    // Substitutes "backslash + new line" with an empty string to imitate line continuation
+    private fun String.addLineContinuation(): String = this.trimIndent().replace("\\\n", "")
+}