Merge changes Ie0400e06,Icac9c6b3

* changes:
  [2/X] Introduce CDM PackageUtils
  [1/X] Introduce CompanionDeviceServiceConnector
diff --git a/ApiDocs.bp b/ApiDocs.bp
index feb43d1..7d4a5e5 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -190,46 +190,6 @@
     },
 }
 
-// This produces the same annotations.zip as framework-doc-stubs, but by using
-// outputs from individual modules instead of all the source code.
-genrule {
-    name: "sdk-annotations.zip",
-    srcs: [
-        ":android-non-updatable-doc-stubs{.annotations.zip}",
-
-        // Conscrypt and i18n currently do not enable annotations
-        // ":conscrypt.module.public.api{.public.annotations.zip}",
-        // ":i18n.module.public.api{.public.annotations.zip}",
-
-        // Modules that enable annotations below
-        ":android.net.ipsec.ike{.public.annotations.zip}",
-        ":art.module.public.api{.public.annotations.zip}",
-        ":framework-appsearch{.public.annotations.zip}",
-        ":framework-connectivity{.public.annotations.zip}",
-        ":framework-connectivity-tiramisu{.public.annotations.zip}",
-        ":framework-graphics{.public.annotations.zip}",
-        ":framework-media{.public.annotations.zip}",
-        ":framework-mediaprovider{.public.annotations.zip}",
-        ":framework-nearby{.public.annotations.zip}",
-        ":framework-permission{.public.annotations.zip}",
-        ":framework-permission-s{.public.annotations.zip}",
-        ":framework-scheduling{.public.annotations.zip}",
-        ":framework-sdkextensions{.public.annotations.zip}",
-        ":framework-statsd{.public.annotations.zip}",
-        ":framework-supplementalprocess{.public.annotations.zip}",
-        ":framework-tethering{.public.annotations.zip}",
-        ":framework-uwb{.public.annotations.zip}",
-        ":framework-wifi{.public.annotations.zip}",
-    ],
-    out: ["annotations.zip"],
-    tools: [
-        "merge_annotation_zips",
-        "soong_zip",
-    ],
-    cmd: "$(location merge_annotation_zips) $(genDir)/out $(in) && " +
-        "$(location soong_zip) -o $(out) -C $(genDir)/out -D $(genDir)/out",
-}
-
 /////////////////////////////////////////////////////////////////////
 // API docs are created from the generated stub source files
 // using droiddoc
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 1e16f94..77b26f8 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -185,6 +185,7 @@
         last_released: {
             api_file: ":android-non-updatable.api.module-lib.latest",
             removed_api_file: ":android-non-updatable-removed.api.module-lib.latest",
+            baseline_file: ":android-non-updatable-incompatibilities.api.module-lib.latest",
         },
         api_lint: {
             enabled: true,
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobInfo.java b/apex/blobstore/framework/java/android/app/blob/BlobInfo.java
index 73ef310..ba92d95 100644
--- a/apex/blobstore/framework/java/android/app/blob/BlobInfo.java
+++ b/apex/blobstore/framework/java/android/app/blob/BlobInfo.java
@@ -48,7 +48,6 @@
         mLeaseInfos = leaseInfos;
     }
 
-    @SuppressWarnings("UnsafeParcelApi")
     private BlobInfo(Parcel in) {
         mId = in.readLong();
         mExpiryTimeMs = in.readLong();
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index 66767e2..9c0c365 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -1408,7 +1408,6 @@
          * Use the {@link #CREATOR}
          * @hide
          */
-        @SuppressWarnings("UnsafeParcelApi")
         AlarmClockInfo(Parcel in) {
             mTriggerTime = in.readLong();
             mShowIntent = in.readParcelable(PendingIntent.class.getClassLoader());
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index b9673f2..0e6006a 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -881,7 +881,6 @@
         return hashCode;
     }
 
-    @SuppressWarnings("UnsafeParcelApi")
     private JobInfo(Parcel in) {
         jobId = in.readInt();
         extras = in.readPersistableBundle();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 86d7a5a..3fb1fad 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -54,6 +54,8 @@
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ServiceInfo;
 import android.net.Uri;
+import android.os.BatteryManager;
+import android.os.BatteryManagerInternal;
 import android.os.BatteryStats;
 import android.os.BatteryStatsInternal;
 import android.os.Binder;
@@ -250,8 +252,6 @@
      */
     private final List<RestrictingController> mRestrictiveControllers;
     /** Need direct access to this for testing. */
-    private final BatteryController mBatteryController;
-    /** Need direct access to this for testing. */
     private final StorageController mStorageController;
     /** Need directly for sending uid state changes */
     private final DeviceIdleJobsController mDeviceIdleJobsController;
@@ -268,6 +268,9 @@
      */
     private final List<JobRestriction> mJobRestrictions;
 
+    @GuardedBy("mLock")
+    private final BatteryStateTracker mBatteryStateTracker;
+
     @NonNull
     private final String mSystemGalleryPackage;
 
@@ -1697,6 +1700,9 @@
         // Initialize the job store and set up any persisted jobs
         mJobs = JobStore.initAndGet(this);
 
+        mBatteryStateTracker = new BatteryStateTracker();
+        mBatteryStateTracker.startTracking();
+
         // Create the controllers.
         mControllers = new ArrayList<StateController>();
         final ConnectivityController connectivityController = new ConnectivityController(this);
@@ -1704,8 +1710,8 @@
         mControllers.add(new TimeController(this));
         final IdleController idleController = new IdleController(this);
         mControllers.add(idleController);
-        mBatteryController = new BatteryController(this);
-        mControllers.add(mBatteryController);
+        final BatteryController batteryController = new BatteryController(this);
+        mControllers.add(batteryController);
         mStorageController = new StorageController(this);
         mControllers.add(mStorageController);
         final BackgroundJobsController backgroundJobsController =
@@ -1725,7 +1731,7 @@
         mControllers.add(mTareController);
 
         mRestrictiveControllers = new ArrayList<>();
-        mRestrictiveControllers.add(mBatteryController);
+        mRestrictiveControllers.add(batteryController);
         mRestrictiveControllers.add(connectivityController);
         mRestrictiveControllers.add(idleController);
 
@@ -2818,6 +2824,129 @@
         return adjustJobBias(bias, job);
     }
 
+    private final class BatteryStateTracker extends BroadcastReceiver {
+        /**
+         * Track whether we're "charging", where charging means that we're ready to commit to
+         * doing work.
+         */
+        private boolean mCharging;
+        /** Keep track of whether the battery is charged enough that we want to do work. */
+        private boolean mBatteryNotLow;
+        /** Sequence number of last broadcast. */
+        private int mLastBatterySeq = -1;
+
+        private BroadcastReceiver mMonitor;
+
+        BatteryStateTracker() {
+        }
+
+        public void startTracking() {
+            IntentFilter filter = new IntentFilter();
+
+            // Battery health.
+            filter.addAction(Intent.ACTION_BATTERY_LOW);
+            filter.addAction(Intent.ACTION_BATTERY_OKAY);
+            // Charging/not charging.
+            filter.addAction(BatteryManager.ACTION_CHARGING);
+            filter.addAction(BatteryManager.ACTION_DISCHARGING);
+            getTestableContext().registerReceiver(this, filter);
+
+            // Initialise tracker state.
+            BatteryManagerInternal batteryManagerInternal =
+                    LocalServices.getService(BatteryManagerInternal.class);
+            mBatteryNotLow = !batteryManagerInternal.getBatteryLevelLow();
+            mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
+        }
+
+        public void setMonitorBatteryLocked(boolean enabled) {
+            if (enabled) {
+                if (mMonitor == null) {
+                    mMonitor = new BroadcastReceiver() {
+                        @Override
+                        public void onReceive(Context context, Intent intent) {
+                            onReceiveInternal(intent);
+                        }
+                    };
+                    IntentFilter filter = new IntentFilter();
+                    filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+                    getTestableContext().registerReceiver(mMonitor, filter);
+                }
+            } else if (mMonitor != null) {
+                getTestableContext().unregisterReceiver(mMonitor);
+                mMonitor = null;
+            }
+        }
+
+        public boolean isCharging() {
+            return mCharging;
+        }
+
+        public boolean isBatteryNotLow() {
+            return mBatteryNotLow;
+        }
+
+        public boolean isMonitoring() {
+            return mMonitor != null;
+        }
+
+        public int getSeq() {
+            return mLastBatterySeq;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            onReceiveInternal(intent);
+        }
+
+        @VisibleForTesting
+        public void onReceiveInternal(Intent intent) {
+            synchronized (mLock) {
+                final String action = intent.getAction();
+                boolean changed = false;
+                if (Intent.ACTION_BATTERY_LOW.equals(action)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Battery life too low @ " + sElapsedRealtimeClock.millis());
+                    }
+                    if (mBatteryNotLow) {
+                        mBatteryNotLow = false;
+                        changed = true;
+                    }
+                } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Battery high enough @ " + sElapsedRealtimeClock.millis());
+                    }
+                    if (!mBatteryNotLow) {
+                        mBatteryNotLow = true;
+                        changed = true;
+                    }
+                } else if (BatteryManager.ACTION_CHARGING.equals(action)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Battery charging @ " + sElapsedRealtimeClock.millis());
+                    }
+                    if (!mCharging) {
+                        mCharging = true;
+                        changed = true;
+                    }
+                } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Disconnected from power @ " + sElapsedRealtimeClock.millis());
+                    }
+                    if (mCharging) {
+                        mCharging = false;
+                        changed = true;
+                    }
+                }
+                mLastBatterySeq =
+                        intent.getIntExtra(BatteryManager.EXTRA_SEQUENCE, mLastBatterySeq);
+                if (changed) {
+                    for (int c = mControllers.size() - 1; c >= 0; --c) {
+                        mControllers.get(c).onBatteryStateChangedLocked();
+                    }
+                }
+            }
+        }
+    }
+
     final class LocalService implements JobSchedulerInternal {
 
         /**
@@ -3417,29 +3546,27 @@
 
     void setMonitorBattery(boolean enabled) {
         synchronized (mLock) {
-            if (mBatteryController != null) {
-                mBatteryController.getTracker().setMonitorBatteryLocked(enabled);
-            }
+            mBatteryStateTracker.setMonitorBatteryLocked(enabled);
         }
     }
 
     int getBatterySeq() {
         synchronized (mLock) {
-            return mBatteryController != null ? mBatteryController.getTracker().getSeq() : -1;
+            return mBatteryStateTracker.getSeq();
         }
     }
 
-    boolean getBatteryCharging() {
+    /** Return {@code true} if the device is currently charging. */
+    public boolean isBatteryCharging() {
         synchronized (mLock) {
-            return mBatteryController != null
-                    ? mBatteryController.getTracker().isOnStablePower() : false;
+            return mBatteryStateTracker.isCharging();
         }
     }
 
-    boolean getBatteryNotLow() {
+    /** Return {@code true} if the battery is not low. */
+    public boolean isBatteryNotLow() {
         synchronized (mLock) {
-            return mBatteryController != null
-                    ? mBatteryController.getTracker().isBatteryNotLow() : false;
+            return mBatteryStateTracker.isBatteryNotLow();
         }
     }
 
@@ -3614,6 +3741,16 @@
             mQuotaTracker.dump(pw);
             pw.println();
 
+            pw.print("Battery charging: ");
+            pw.println(mBatteryStateTracker.isCharging());
+            pw.print("Battery not low: ");
+            pw.println(mBatteryStateTracker.isBatteryNotLow());
+            if (mBatteryStateTracker.isMonitoring()) {
+                pw.print("MONITORING: seq=");
+                pw.println(mBatteryStateTracker.getSeq());
+            }
+            pw.println();
+
             pw.println("Started users: " + Arrays.toString(mStartedUsers));
             pw.print("Registered ");
             pw.print(mJobs.size());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index cc20213..27268d2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -293,13 +293,13 @@
     }
 
     private int getBatteryCharging(PrintWriter pw) {
-        boolean val = mInternal.getBatteryCharging();
+        boolean val = mInternal.isBatteryCharging();
         pw.println(val);
         return 0;
     }
 
     private int getBatteryNotLow(PrintWriter pw) {
-        boolean val = mInternal.getBatteryNotLow();
+        boolean val = mInternal.isBatteryNotLow();
         pw.println(val);
         return 0;
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
index 657f470..f733849 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
@@ -18,12 +18,6 @@
 
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.BatteryManager;
-import android.os.BatteryManagerInternal;
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
@@ -31,8 +25,8 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.LocalServices;
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.JobSchedulerBackgroundThread;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateControllerProto;
 
@@ -49,17 +43,9 @@
             || Log.isLoggable(TAG, Log.DEBUG);
 
     private final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>();
-    private ChargingTracker mChargeTracker;
-
-    @VisibleForTesting
-    public ChargingTracker getTracker() {
-        return mChargeTracker;
-    }
 
     public BatteryController(JobSchedulerService service) {
         super(service);
-        mChargeTracker = new ChargingTracker();
-        mChargeTracker.startTracking();
     }
 
     @Override
@@ -68,9 +54,9 @@
             final long nowElapsed = sElapsedRealtimeClock.millis();
             mTrackedTasks.add(taskStatus);
             taskStatus.setTrackingController(JobStatus.TRACKING_BATTERY);
-            taskStatus.setChargingConstraintSatisfied(nowElapsed, mChargeTracker.isOnStablePower());
-            taskStatus.setBatteryNotLowConstraintSatisfied(
-                    nowElapsed, mChargeTracker.isBatteryNotLow());
+            taskStatus.setChargingConstraintSatisfied(nowElapsed,
+                    mService.isBatteryCharging() && mService.isBatteryNotLow());
+            taskStatus.setBatteryNotLowConstraintSatisfied(nowElapsed, mService.isBatteryNotLow());
         }
     }
 
@@ -93,9 +79,21 @@
         }
     }
 
+    @Override
+    @GuardedBy("mLock")
+    public void onBatteryStateChangedLocked() {
+        // Update job bookkeeping out of band.
+        JobSchedulerBackgroundThread.getHandler().post(() -> {
+            synchronized (mLock) {
+                maybeReportNewChargingStateLocked();
+            }
+        });
+    }
+
+    @GuardedBy("mLock")
     private void maybeReportNewChargingStateLocked() {
-        final boolean stablePower = mChargeTracker.isOnStablePower();
-        final boolean batteryNotLow = mChargeTracker.isBatteryNotLow();
+        final boolean stablePower = mService.isBatteryCharging() && mService.isBatteryNotLow();
+        final boolean batteryNotLow = mService.isBatteryNotLow();
         if (DEBUG) {
             Slog.d(TAG, "maybeReportNewChargingStateLocked: " + stablePower);
         }
@@ -116,133 +114,11 @@
         }
     }
 
-    public final class ChargingTracker extends BroadcastReceiver {
-        /**
-         * Track whether we're "charging", where charging means that we're ready to commit to
-         * doing work.
-         */
-        private boolean mCharging;
-        /** Keep track of whether the battery is charged enough that we want to do work. */
-        private boolean mBatteryHealthy;
-        /** Sequence number of last broadcast. */
-        private int mLastBatterySeq = -1;
-
-        private BroadcastReceiver mMonitor;
-
-        public ChargingTracker() {
-        }
-
-        public void startTracking() {
-            IntentFilter filter = new IntentFilter();
-
-            // Battery health.
-            filter.addAction(Intent.ACTION_BATTERY_LOW);
-            filter.addAction(Intent.ACTION_BATTERY_OKAY);
-            // Charging/not charging.
-            filter.addAction(BatteryManager.ACTION_CHARGING);
-            filter.addAction(BatteryManager.ACTION_DISCHARGING);
-            mContext.registerReceiver(this, filter);
-
-            // Initialise tracker state.
-            BatteryManagerInternal batteryManagerInternal =
-                    LocalServices.getService(BatteryManagerInternal.class);
-            mBatteryHealthy = !batteryManagerInternal.getBatteryLevelLow();
-            mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
-        }
-
-        public void setMonitorBatteryLocked(boolean enabled) {
-            if (enabled) {
-                if (mMonitor == null) {
-                    mMonitor = new BroadcastReceiver() {
-                        @Override public void onReceive(Context context, Intent intent) {
-                            ChargingTracker.this.onReceive(context, intent);
-                        }
-                    };
-                    IntentFilter filter = new IntentFilter();
-                    filter.addAction(Intent.ACTION_BATTERY_CHANGED);
-                    mContext.registerReceiver(mMonitor, filter);
-                }
-            } else {
-                if (mMonitor != null) {
-                    mContext.unregisterReceiver(mMonitor);
-                    mMonitor = null;
-                }
-            }
-        }
-
-        public boolean isOnStablePower() {
-            return mCharging && mBatteryHealthy;
-        }
-
-        public boolean isBatteryNotLow() {
-            return mBatteryHealthy;
-        }
-
-        public boolean isMonitoring() {
-            return mMonitor != null;
-        }
-
-        public int getSeq() {
-            return mLastBatterySeq;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            onReceiveInternal(intent);
-        }
-
-        @VisibleForTesting
-        public void onReceiveInternal(Intent intent) {
-            synchronized (mLock) {
-                final String action = intent.getAction();
-                if (Intent.ACTION_BATTERY_LOW.equals(action)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Battery life too low to do work. @ "
-                                + sElapsedRealtimeClock.millis());
-                    }
-                    // If we get this action, the battery is discharging => it isn't plugged in so
-                    // there's no work to cancel. We track this variable for the case where it is
-                    // charging, but hasn't been for long enough to be healthy.
-                    mBatteryHealthy = false;
-                    maybeReportNewChargingStateLocked();
-                } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Battery life healthy enough to do work. @ "
-                                + sElapsedRealtimeClock.millis());
-                    }
-                    mBatteryHealthy = true;
-                    maybeReportNewChargingStateLocked();
-                } else if (BatteryManager.ACTION_CHARGING.equals(action)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Received charging intent, fired @ "
-                                + sElapsedRealtimeClock.millis());
-                    }
-                    mCharging = true;
-                    maybeReportNewChargingStateLocked();
-                } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Disconnected from power.");
-                    }
-                    mCharging = false;
-                    maybeReportNewChargingStateLocked();
-                }
-                mLastBatterySeq = intent.getIntExtra(BatteryManager.EXTRA_SEQUENCE,
-                        mLastBatterySeq);
-            }
-        }
-    }
-
     @Override
     public void dumpControllerStateLocked(IndentingPrintWriter pw,
             Predicate<JobStatus> predicate) {
-        pw.println("Stable power: " + mChargeTracker.isOnStablePower());
-        pw.println("Not low: " + mChargeTracker.isBatteryNotLow());
-
-        if (mChargeTracker.isMonitoring()) {
-            pw.print("MONITORING: seq=");
-            pw.println(mChargeTracker.getSeq());
-        }
-        pw.println();
+        pw.println("Stable power: " + (mService.isBatteryCharging() && mService.isBatteryNotLow()));
+        pw.println("Not low: " + mService.isBatteryNotLow());
 
         for (int i = 0; i < mTrackedTasks.size(); i++) {
             final JobStatus js = mTrackedTasks.valueAt(i);
@@ -264,14 +140,9 @@
         final long mToken = proto.start(StateControllerProto.BATTERY);
 
         proto.write(StateControllerProto.BatteryController.IS_ON_STABLE_POWER,
-                mChargeTracker.isOnStablePower());
+                mService.isBatteryCharging() && mService.isBatteryNotLow());
         proto.write(StateControllerProto.BatteryController.IS_BATTERY_NOT_LOW,
-                mChargeTracker.isBatteryNotLow());
-
-        proto.write(StateControllerProto.BatteryController.IS_MONITORING,
-                mChargeTracker.isMonitoring());
-        proto.write(StateControllerProto.BatteryController.LAST_BROADCAST_SEQUENCE_NUMBER,
-                mChargeTracker.getSeq());
+                mService.isBatteryNotLow());
 
         for (int i = 0; i < mTrackedTasks.size(); i++) {
             final JobStatus js = mTrackedTasks.valueAt(i);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 524d68f..9fb7ab5 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -25,18 +25,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.job.JobInfo;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkPolicyManager;
 import android.net.NetworkRequest;
-import android.os.BatteryManager;
-import android.os.BatteryManagerInternal;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -55,6 +49,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.JobSchedulerBackgroundThread;
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.JobSchedulerService.Constants;
@@ -107,8 +102,6 @@
     private final ConnectivityManager mConnManager;
     private final NetworkPolicyManagerInternal mNetPolicyManagerInternal;
 
-    private final ChargingTracker mChargingTracker;
-
     /** List of tracked jobs keyed by source UID. */
     @GuardedBy("mLock")
     private final SparseArray<ArraySet<JobStatus>> mTrackedJobs = new SparseArray<>();
@@ -237,9 +230,6 @@
         // network changes against the active network for each UID with jobs.
         final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
         mConnManager.registerNetworkCallback(request, mNetworkCallback);
-
-        mChargingTracker = new ChargingTracker();
-        mChargingTracker.startTracking();
     }
 
     @GuardedBy("mLock")
@@ -535,6 +525,17 @@
         }
     }
 
+    @Override
+    @GuardedBy("mLock")
+    public void onBatteryStateChangedLocked() {
+        // Update job bookkeeping out of band to avoid blocking broadcast progress.
+        JobSchedulerBackgroundThread.getHandler().post(() -> {
+            synchronized (mLock) {
+                updateTrackedJobsLocked(-1, null);
+            }
+        });
+    }
+
     private boolean isUsable(NetworkCapabilities capabilities) {
         return capabilities != null
                 && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
@@ -591,7 +592,7 @@
         // Minimum chunk size isn't defined. Check using the estimated upload/download sizes.
 
         if (capabilities.hasCapability(NET_CAPABILITY_NOT_METERED)
-                && mChargingTracker.isCharging()) {
+                && mService.isBatteryCharging()) {
             // We're charging and on an unmetered network. We don't have to be as conservative about
             // making sure the job will run within its max execution time. Let's just hope the app
             // supports interruptible work.
@@ -1072,51 +1073,6 @@
         }
     }
 
-    private final class ChargingTracker extends BroadcastReceiver {
-        /**
-         * Track whether we're "charging", where charging means that we're ready to commit to
-         * doing work.
-         */
-        private boolean mCharging;
-
-        ChargingTracker() {}
-
-        public void startTracking() {
-            IntentFilter filter = new IntentFilter();
-            filter.addAction(BatteryManager.ACTION_CHARGING);
-            filter.addAction(BatteryManager.ACTION_DISCHARGING);
-            mContext.registerReceiver(this, filter);
-
-            // Initialise tracker state.
-            final BatteryManagerInternal batteryManagerInternal =
-                    LocalServices.getService(BatteryManagerInternal.class);
-            mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
-        }
-
-        public boolean isCharging() {
-            return mCharging;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            synchronized (mLock) {
-                final String action = intent.getAction();
-                if (BatteryManager.ACTION_CHARGING.equals(action)) {
-                    if (mCharging) {
-                        return;
-                    }
-                    mCharging = true;
-                } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
-                    if (!mCharging) {
-                        return;
-                    }
-                    mCharging = false;
-                }
-                updateTrackedJobsLocked(-1, null);
-            }
-        }
-    }
-
     private final NetworkCallback mNetworkCallback = new NetworkCallback() {
         @Override
         public void onAvailable(Network network) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index c147ef8..65e1d49 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -39,15 +39,11 @@
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal;
 import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
-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.PackageInfo;
 import android.content.pm.PackageManager;
 import android.os.BatteryManager;
-import android.os.BatteryManagerInternal;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -319,7 +315,6 @@
     private final SparseLongArray mTopAppGraceCache = new SparseLongArray();
 
     private final AlarmManager mAlarmManager;
-    private final ChargingTracker mChargeTracker;
     private final QcHandler mHandler;
     private final QcConstants mQcConstants;
 
@@ -542,8 +537,6 @@
             @NonNull ConnectivityController connectivityController) {
         super(service);
         mHandler = new QcHandler(mContext.getMainLooper());
-        mChargeTracker = new ChargingTracker();
-        mChargeTracker.startTracking();
         mAlarmManager = mContext.getSystemService(AlarmManager.class);
         mQcConstants = new QcConstants();
         mBackgroundJobsController = backgroundJobsController;
@@ -705,6 +698,11 @@
         mTopAppTrackers.delete(userId);
     }
 
+    @Override
+    public void onBatteryStateChangedLocked() {
+        handleNewChargingStateLocked();
+    }
+
     /** Drop all historical stats and stop tracking any active sessions for the specified app. */
     public void clearAppStatsLocked(int userId, @NonNull String packageName) {
         mTrackedJobs.delete(userId, packageName);
@@ -766,7 +764,7 @@
         if (!jobStatus.shouldTreatAsExpeditedJob()) {
             // If quota is currently "free", then the job can run for the full amount of time,
             // regardless of bucket (hence using charging instead of isQuotaFreeLocked()).
-            if (mChargeTracker.isChargingLocked()
+            if (mService.isBatteryCharging()
                     || mTopAppCache.get(jobStatus.getSourceUid())
                     || isTopStartedJobLocked(jobStatus)
                     || isUidInForeground(jobStatus.getSourceUid())) {
@@ -777,7 +775,7 @@
         }
 
         // Expedited job.
-        if (mChargeTracker.isChargingLocked()) {
+        if (mService.isBatteryCharging()) {
             return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
         }
         if (mTopAppCache.get(jobStatus.getSourceUid()) || isTopStartedJobLocked(jobStatus)) {
@@ -864,7 +862,7 @@
     @GuardedBy("mLock")
     private boolean isQuotaFreeLocked(final int standbyBucket) {
         // Quota constraint is not enforced while charging.
-        if (mChargeTracker.isChargingLocked()) {
+        if (mService.isBatteryCharging()) {
             // Restricted jobs require additional constraints when charging, so don't immediately
             // mark quota as free when charging.
             return standbyBucket != RESTRICTED_INDEX;
@@ -1538,15 +1536,19 @@
 
     private void handleNewChargingStateLocked() {
         mTimerChargingUpdateFunctor.setStatus(sElapsedRealtimeClock.millis(),
-                mChargeTracker.isChargingLocked());
+                mService.isBatteryCharging());
         if (DEBUG) {
-            Slog.d(TAG, "handleNewChargingStateLocked: " + mChargeTracker.isChargingLocked());
+            Slog.d(TAG, "handleNewChargingStateLocked: " + mService.isBatteryCharging());
         }
         // Deal with Timers first.
         mEJPkgTimers.forEach(mTimerChargingUpdateFunctor);
         mPkgTimers.forEach(mTimerChargingUpdateFunctor);
-        // Now update jobs.
-        maybeUpdateAllConstraintsLocked();
+        // Now update jobs out of band so broadcast processing can proceed.
+        JobSchedulerBackgroundThread.getHandler().post(() -> {
+            synchronized (mLock) {
+                maybeUpdateAllConstraintsLocked();
+            }
+        });
     }
 
     private void maybeUpdateAllConstraintsLocked() {
@@ -1811,58 +1813,6 @@
         return false;
     }
 
-    private final class ChargingTracker extends BroadcastReceiver {
-        /**
-         * Track whether we're charging. This has a slightly different definition than that of
-         * BatteryController.
-         */
-        @GuardedBy("mLock")
-        private boolean mCharging;
-
-        ChargingTracker() {
-        }
-
-        public void startTracking() {
-            IntentFilter filter = new IntentFilter();
-
-            // Charging/not charging.
-            filter.addAction(BatteryManager.ACTION_CHARGING);
-            filter.addAction(BatteryManager.ACTION_DISCHARGING);
-            mContext.registerReceiver(this, filter);
-
-            // Initialise tracker state.
-            BatteryManagerInternal batteryManagerInternal =
-                    LocalServices.getService(BatteryManagerInternal.class);
-            mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
-        }
-
-        @GuardedBy("mLock")
-        public boolean isChargingLocked() {
-            return mCharging;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            synchronized (mLock) {
-                final String action = intent.getAction();
-                if (BatteryManager.ACTION_CHARGING.equals(action)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Received charging intent, fired @ "
-                                + sElapsedRealtimeClock.millis());
-                    }
-                    mCharging = true;
-                    handleNewChargingStateLocked();
-                } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Disconnected from power.");
-                    }
-                    mCharging = false;
-                    handleNewChargingStateLocked();
-                }
-            }
-        }
-    }
-
     @VisibleForTesting
     static final class TimingSession {
         // Start timestamp in elapsed realtime timebase.
@@ -2127,6 +2077,12 @@
             final long topAppGracePeriodEndElapsed = mTopAppGraceCache.get(mUid);
             final boolean hasTopAppExemption = !mRegularJobTimer
                     && (mTopAppCache.get(mUid) || nowElapsed < topAppGracePeriodEndElapsed);
+            if (DEBUG) {
+                Slog.d(TAG, "quotaFree=" + isQuotaFreeLocked(standbyBucket)
+                        + " isFG=" + mForegroundUids.get(mUid)
+                        + " tempEx=" + hasTempAllowlistExemption
+                        + " topEx=" + hasTopAppExemption);
+            }
             return !isQuotaFreeLocked(standbyBucket)
                     && !mForegroundUids.get(mUid) && !hasTempAllowlistExemption
                     && !hasTopAppExemption;
@@ -3938,7 +3894,6 @@
     public void dumpControllerStateLocked(final IndentingPrintWriter pw,
             final Predicate<JobStatus> predicate) {
         pw.println("Is enabled: " + mIsEnabled);
-        pw.println("Is charging: " + mChargeTracker.isChargingLocked());
         pw.println("Current elapsed time: " + sElapsedRealtimeClock.millis());
         pw.println();
 
@@ -4116,7 +4071,7 @@
         final long mToken = proto.start(StateControllerProto.QUOTA);
 
         proto.write(StateControllerProto.QuotaController.IS_CHARGING,
-                mChargeTracker.isChargingLocked());
+                mService.isBatteryCharging());
         proto.write(StateControllerProto.QuotaController.ELAPSED_REALTIME,
                 sElapsedRealtimeClock.millis());
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
index 5e73f42..509fb69 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
@@ -133,6 +133,13 @@
     }
 
     /**
+     * Called when the battery status changes.
+     */
+    @GuardedBy("mLock")
+    public void onBatteryStateChangedLocked() {
+    }
+
+    /**
      * Called when a UID's base bias has changed. The more positive the bias, the more
      * important the UID is.
      */
diff --git a/apex/media/OWNERS b/apex/media/OWNERS
index bed3895..2c5965c 100644
--- a/apex/media/OWNERS
+++ b/apex/media/OWNERS
@@ -1,6 +1,5 @@
 # Bug component: 1344
 hdmoon@google.com
-hkuang@google.com
 jinpark@google.com
 klhyun@google.com
 lnilsson@google.com
diff --git a/apex/media/framework/jarjar_rules.txt b/apex/media/framework/jarjar_rules.txt
index 1bd5b36..91489dc 100644
--- a/apex/media/framework/jarjar_rules.txt
+++ b/apex/media/framework/jarjar_rules.txt
@@ -1,4 +1,2 @@
 rule com.android.modules.** android.media.internal.@1
 rule com.google.android.exoplayer2.** android.media.internal.exo.@1
-rule com.google.common.** android.media.internal.guava_common.@1
-rule com.google.thirdparty.** android.media.internal.guava_thirdparty.@1
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index 7daebd0..b6f85c7 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -62,8 +62,8 @@
 import com.google.android.exoplayer2.upstream.DataReader;
 import com.google.android.exoplayer2.util.ParsableByteArray;
 import com.google.android.exoplayer2.util.TimestampAdjuster;
+import com.google.android.exoplayer2.util.Util;
 import com.google.android.exoplayer2.video.ColorInfo;
-import com.google.common.base.Ascii;
 
 import java.io.EOFException;
 import java.io.IOException;
@@ -978,7 +978,7 @@
     @ParserName
     public static List<String> getParserNames(@NonNull MediaFormat mediaFormat) {
         String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
-        mimeType = mimeType == null ? null : Ascii.toLowerCase(mimeType);
+        mimeType = mimeType == null ? null : Util.toLowerInvariant(mimeType.trim());
         if (TextUtils.isEmpty(mimeType)) {
             // No MIME type provided. Return all.
             return Collections.unmodifiableList(
@@ -1420,7 +1420,7 @@
         int flags = 0;
         TimestampAdjuster timestampAdjuster = null;
         if (mIgnoreTimestampOffset) {
-            timestampAdjuster = new TimestampAdjuster(TimestampAdjuster.MODE_NO_OFFSET);
+            timestampAdjuster = new TimestampAdjuster(TimestampAdjuster.DO_NOT_OFFSET);
         }
         switch (parserName) {
             case PARSER_NAME_MATROSKA:
diff --git a/apex/media/framework/java/android/media/Session2CommandGroup.java b/apex/media/framework/java/android/media/Session2CommandGroup.java
index 13aabfc..af8184a 100644
--- a/apex/media/framework/java/android/media/Session2CommandGroup.java
+++ b/apex/media/framework/java/android/media/Session2CommandGroup.java
@@ -68,7 +68,7 @@
     /**
      * Used by parcelable creator.
      */
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    @SuppressWarnings({"WeakerAccess", "UnsafeParcelApi"}) /* synthetic access */
     Session2CommandGroup(Parcel in) {
         Parcelable[] commands = in.readParcelableArray(Session2Command.class.getClassLoader());
         if (commands != null) {
diff --git a/apex/media/service/java/com/android/server/media/MediaCommunicationService.java b/apex/media/service/java/com/android/server/media/MediaCommunicationService.java
index e48f234..7d47e25 100644
--- a/apex/media/service/java/com/android/server/media/MediaCommunicationService.java
+++ b/apex/media/service/java/com/android/server/media/MediaCommunicationService.java
@@ -79,7 +79,7 @@
 
     final Executor mRecordExecutor = Executors.newSingleThreadExecutor();
     @GuardedBy("mLock")
-    final List<CallbackRecord> mCallbackRecords = new ArrayList<>();
+    final ArrayList<CallbackRecord> mCallbackRecords = new ArrayList<>();
     final NotificationManager mNotificationManager;
     MediaSessionManager mSessionManager;
 
@@ -150,8 +150,8 @@
         return null;
     }
 
-    List<Session2Token> getSession2TokensLocked(int userId) {
-        List<Session2Token> list = new ArrayList<>();
+    ArrayList<Session2Token> getSession2TokensLocked(int userId) {
+        ArrayList<Session2Token> list = new ArrayList<>();
         if (userId == ALL.getIdentifier()) {
             int size = mUserRecords.size();
             for (int i = 0; i < size; i++) {
@@ -237,28 +237,29 @@
     }
 
     void dispatchSession2Changed(int userId) {
-        MediaParceledListSlice<Session2Token> allSession2Tokens;
-        MediaParceledListSlice<Session2Token> userSession2Tokens;
+        ArrayList<Session2Token> allSession2Tokens;
+        ArrayList<Session2Token> userSession2Tokens;
 
         synchronized (mLock) {
-            allSession2Tokens =
-                    new MediaParceledListSlice<>(getSession2TokensLocked(ALL.getIdentifier()));
-            userSession2Tokens = new MediaParceledListSlice<>(getSession2TokensLocked(userId));
-        }
-        allSession2Tokens.setInlineCountLimit(1);
-        userSession2Tokens.setInlineCountLimit(1);
+            allSession2Tokens = getSession2TokensLocked(ALL.getIdentifier());
+            userSession2Tokens = getSession2TokensLocked(userId);
 
-        synchronized (mLock) {
             for (CallbackRecord record : mCallbackRecords) {
                 if (record.mUserId == ALL.getIdentifier()) {
                     try {
-                        record.mCallback.onSession2Changed(allSession2Tokens);
+                        MediaParceledListSlice<Session2Token> toSend =
+                                new MediaParceledListSlice<>(allSession2Tokens);
+                        toSend.setInlineCountLimit(0);
+                        record.mCallback.onSession2Changed(toSend);
                     } catch (RemoteException e) {
                         Log.w(TAG, "Failed to notify session2 tokens changed " + record);
                     }
                 } else if (record.mUserId == userId) {
                     try {
-                        record.mCallback.onSession2Changed(userSession2Tokens);
+                        MediaParceledListSlice<Session2Token> toSend =
+                                new MediaParceledListSlice<>(userSession2Tokens);
+                        toSend.setInlineCountLimit(0);
+                        record.mCallback.onSession2Changed(toSend);
                     } catch (RemoteException e) {
                         Log.w(TAG, "Failed to notify session2 tokens changed " + record);
                     }
@@ -382,7 +383,7 @@
             try {
                 // Check that they can make calls on behalf of the user and get the final user id
                 int resolvedUserId = handleIncomingUser(pid, uid, userId, null);
-                List<Session2Token> result;
+                ArrayList<Session2Token> result;
                 synchronized (mLock) {
                     result = getSession2TokensLocked(resolvedUserId);
                 }
diff --git a/api/Android.bp b/api/Android.bp
index 2e49a0c..7aa68f8 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -24,6 +24,19 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+bootstrap_go_package {
+    name: "soong-api",
+    pkgPath: "android/soong/api",
+    deps: [
+        "blueprint",
+        "soong",
+        "soong-android",
+        "soong-genrule",
+    ],
+    srcs: ["api.go"],
+    pluginFor: ["soong_build"],
+}
+
 python_defaults {
     name: "python3_version_defaults",
     version: {
@@ -89,47 +102,37 @@
     visibility: ["//visibility:public"],
 }
 
-genrule {
-    name: "frameworks-base-api-current.txt",
-    srcs: [
-        ":android.net.ipsec.ike{.public.api.txt}",
-        ":art.module.public.api{.public.api.txt}",
-        ":conscrypt.module.public.api{.public.api.txt}",
-        ":framework-appsearch{.public.api.txt}",
-        ":framework-connectivity{.public.api.txt}",
-        ":framework-connectivity-tiramisu{.public.api.txt}",
-        ":framework-graphics{.public.api.txt}",
-        ":framework-media{.public.api.txt}",
-        ":framework-mediaprovider{.public.api.txt}",
-        ":framework-nearby{.public.api.txt}",
-        ":framework-permission{.public.api.txt}",
-        ":framework-permission-s{.public.api.txt}",
-        ":framework-scheduling{.public.api.txt}",
-        ":framework-sdkextensions{.public.api.txt}",
-        ":framework-statsd{.public.api.txt}",
-        ":framework-supplementalprocess{.public.api.txt}",
-        ":framework-tethering{.public.api.txt}",
-        ":framework-uwb{.public.api.txt}",
-        ":framework-wifi{.public.api.txt}",
-        ":i18n.module.public.api{.public.api.txt}",
-        ":non-updatable-current.txt",
+combined_apis {
+    name: "frameworks-base-api",
+    bootclasspath: [
+        "android.net.ipsec.ike",
+        "art.module.public.api",
+        "conscrypt.module.public.api",
+        "framework-appsearch",
+        "framework-connectivity",
+        "framework-connectivity-tiramisu",
+        "framework-graphics",
+        "framework-media",
+        "framework-mediaprovider",
+        "framework-nearby",
+        "framework-permission",
+        "framework-permission-s",
+        "framework-scheduling",
+        "framework-sdkextensions",
+        "framework-statsd",
+        "framework-supplementalprocess",
+        "framework-tethering",
+        "framework-uwb",
+        "framework-wifi",
+        "i18n.module.public.api",
     ],
-    out: ["current.txt"],
-    tools: ["metalava"],
-    cmd: metalava_cmd + "$(in) --api $(out)",
-    dists: [
-        {
-            targets: ["droidcore"],
-            dir: "api",
-            dest: "current.txt",
-        },
-        {
-            targets: ["sdk"],
-            dir: "apistubs/android/public/api",
-            dest: "android.txt",
-        },
+    conditional_bootclasspath: [
+        "framework-supplementalapi",
     ],
-    visibility: ["//visibility:public"],
+    system_server_classpath: [
+        "service-media-s",
+        "service-permission",
+    ],
 }
 
 genrule {
@@ -149,120 +152,6 @@
 }
 
 genrule {
-    name: "frameworks-base-api-current.srcjar",
-    srcs: [
-        ":android.net.ipsec.ike{.public.stubs.source}",
-        ":api-stubs-docs-non-updatable",
-        ":art.module.public.api{.public.stubs.source}",
-        ":conscrypt.module.public.api{.public.stubs.source}",
-        ":framework-appsearch{.public.stubs.source}",
-        ":framework-connectivity{.public.stubs.source}",
-        ":framework-connectivity-tiramisu{.public.stubs.source}",
-        ":framework-graphics{.public.stubs.source}",
-        ":framework-media{.public.stubs.source}",
-        ":framework-mediaprovider{.public.stubs.source}",
-        ":framework-nearby{.public.stubs.source}",
-        ":framework-permission{.public.stubs.source}",
-        ":framework-permission-s{.public.stubs.source}",
-        ":framework-scheduling{.public.stubs.source}",
-        ":framework-sdkextensions{.public.stubs.source}",
-        ":framework-statsd{.public.stubs.source}",
-        ":framework-supplementalprocess{.public.stubs.source}",
-        ":framework-tethering{.public.stubs.source}",
-        ":framework-uwb{.public.stubs.source}",
-        ":framework-wifi{.public.stubs.source}",
-        ":i18n.module.public.api{.public.stubs.source}",
-    ],
-    out: ["current.srcjar"],
-    tools: ["merge_zips"],
-    cmd: "$(location merge_zips) $(out) $(in)",
-    visibility: ["//visibility:private"], // Used by make module in //development, mind.
-}
-
-genrule {
-    name: "frameworks-base-api-removed.txt",
-    srcs: [
-        ":android.net.ipsec.ike{.public.removed-api.txt}",
-        ":art.module.public.api{.public.removed-api.txt}",
-        ":conscrypt.module.public.api{.public.removed-api.txt}",
-        ":framework-appsearch{.public.removed-api.txt}",
-        ":framework-connectivity{.public.removed-api.txt}",
-        ":framework-connectivity-tiramisu{.public.removed-api.txt}",
-        ":framework-graphics{.public.removed-api.txt}",
-        ":framework-media{.public.removed-api.txt}",
-        ":framework-mediaprovider{.public.removed-api.txt}",
-        ":framework-nearby{.public.removed-api.txt}",
-        ":framework-permission{.public.removed-api.txt}",
-        ":framework-permission-s{.public.removed-api.txt}",
-        ":framework-scheduling{.public.removed-api.txt}",
-        ":framework-sdkextensions{.public.removed-api.txt}",
-        ":framework-statsd{.public.removed-api.txt}",
-        ":framework-supplementalprocess{.public.removed-api.txt}",
-        ":framework-tethering{.public.removed-api.txt}",
-        ":framework-uwb{.public.removed-api.txt}",
-        ":framework-wifi{.public.removed-api.txt}",
-        ":i18n.module.public.api{.public.removed-api.txt}",
-        ":non-updatable-removed.txt",
-    ],
-    out: ["removed.txt"],
-    tools: ["metalava"],
-    cmd: metalava_cmd + "$(in) --api $(out)",
-    dists: [
-        {
-            targets: ["droidcore"],
-            dir: "api",
-            dest: "removed.txt",
-        },
-        {
-            targets: ["sdk"],
-            dir: "apistubs/android/public/api",
-            dest: "removed.txt",
-        },
-    ],
-}
-
-genrule {
-    name: "frameworks-base-api-system-current.txt",
-    srcs: [
-        ":art.module.public.api{.system.api.txt}",
-        ":android.net.ipsec.ike{.system.api.txt}",
-        ":framework-appsearch{.system.api.txt}",
-        ":framework-connectivity{.system.api.txt}",
-        ":framework-connectivity-tiramisu{.system.api.txt}",
-        ":framework-graphics{.system.api.txt}",
-        ":framework-media{.system.api.txt}",
-        ":framework-mediaprovider{.system.api.txt}",
-        ":framework-nearby{.system.api.txt}",
-        ":framework-permission{.system.api.txt}",
-        ":framework-permission-s{.system.api.txt}",
-        ":framework-scheduling{.system.api.txt}",
-        ":framework-sdkextensions{.system.api.txt}",
-        ":framework-statsd{.system.api.txt}",
-        ":framework-supplementalprocess{.system.api.txt}",
-        ":framework-tethering{.system.api.txt}",
-        ":framework-uwb{.system.api.txt}",
-        ":framework-wifi{.system.api.txt}",
-        ":non-updatable-system-current.txt",
-    ],
-    out: ["system-current.txt"],
-    tools: ["metalava"],
-    cmd: metalava_cmd + "$(in) --api $(out)",
-    dists: [
-        {
-            targets: ["droidcore"],
-            dir: "api",
-            dest: "system-current.txt",
-        },
-        {
-            targets: ["sdk"],
-            dir: "apistubs/android/system/api",
-            dest: "android.txt",
-        },
-    ],
-    visibility: ["//visibility:public"],
-}
-
-genrule {
     name: "frameworks-base-api-system-current-compat",
     srcs: [
         ":android.api.system.latest",
@@ -281,87 +170,6 @@
 }
 
 genrule {
-    name: "frameworks-base-api-system-removed.txt",
-    srcs: [
-        ":art.module.public.api{.system.removed-api.txt}",
-        ":android.net.ipsec.ike{.system.removed-api.txt}",
-        ":framework-appsearch{.system.removed-api.txt}",
-        ":framework-connectivity{.system.removed-api.txt}",
-        ":framework-connectivity-tiramisu{.system.removed-api.txt}",
-        ":framework-graphics{.system.removed-api.txt}",
-        ":framework-media{.system.removed-api.txt}",
-        ":framework-mediaprovider{.system.removed-api.txt}",
-        ":framework-nearby{.system.removed-api.txt}",
-        ":framework-permission{.system.removed-api.txt}",
-        ":framework-permission-s{.system.removed-api.txt}",
-        ":framework-scheduling{.system.removed-api.txt}",
-        ":framework-sdkextensions{.system.removed-api.txt}",
-        ":framework-statsd{.system.removed-api.txt}",
-        ":framework-supplementalprocess{.system.removed-api.txt}",
-        ":framework-tethering{.system.removed-api.txt}",
-        ":framework-uwb{.system.removed-api.txt}",
-        ":framework-wifi{.system.removed-api.txt}",
-        ":non-updatable-system-removed.txt",
-    ],
-    out: ["system-removed.txt"],
-    tools: ["metalava"],
-    cmd: metalava_cmd + "$(in) --api $(out)",
-    dists: [
-        {
-            targets: ["droidcore"],
-            dir: "api",
-            dest: "system-removed.txt",
-        },
-        {
-            targets: ["sdk"],
-            dir: "apistubs/android/system/api",
-            dest: "removed.txt",
-        },
-    ],
-    visibility: ["//visibility:public"],
-}
-
-genrule {
-    name: "frameworks-base-api-module-lib-current.txt",
-    srcs: [
-        ":art.module.public.api{.module-lib.api.txt}",
-        ":android.net.ipsec.ike{.module-lib.api.txt}",
-        ":framework-appsearch{.module-lib.api.txt}",
-        ":framework-connectivity{.module-lib.api.txt}",
-        ":framework-connectivity-tiramisu{.module-lib.api.txt}",
-        ":framework-graphics{.module-lib.api.txt}",
-        ":framework-media{.module-lib.api.txt}",
-        ":framework-mediaprovider{.module-lib.api.txt}",
-        ":framework-nearby{.module-lib.api.txt}",
-        ":framework-permission{.module-lib.api.txt}",
-        ":framework-permission-s{.module-lib.api.txt}",
-        ":framework-scheduling{.module-lib.api.txt}",
-        ":framework-sdkextensions{.module-lib.api.txt}",
-        ":framework-statsd{.module-lib.api.txt}",
-        ":framework-supplementalprocess{.module-lib.api.txt}",
-        ":framework-tethering{.module-lib.api.txt}",
-        ":framework-uwb{.module-lib.api.txt}",
-        ":framework-wifi{.module-lib.api.txt}",
-        ":non-updatable-module-lib-current.txt",
-    ],
-    out: ["module-lib-current.txt"],
-    tools: ["metalava"],
-    cmd: metalava_cmd + "$(in) --api $(out)",
-    dists: [
-        {
-            targets: ["droidcore"],
-            dir: "api",
-            dest: "module-lib-current.txt",
-        },
-        {
-            targets: ["sdk"],
-            dir: "apistubs/android/module-lib/api",
-            dest: "android.txt",
-        },
-    ],
-}
-
-genrule {
     name: "frameworks-base-api-module-lib-current-compat",
     srcs: [
         ":android.api.module-lib.latest",
@@ -383,46 +191,6 @@
 }
 
 genrule {
-    name: "frameworks-base-api-module-lib-removed.txt",
-    srcs: [
-        ":art.module.public.api{.module-lib.removed-api.txt}",
-        ":android.net.ipsec.ike{.module-lib.removed-api.txt}",
-        ":framework-appsearch{.module-lib.removed-api.txt}",
-        ":framework-connectivity{.module-lib.removed-api.txt}",
-        ":framework-connectivity-tiramisu{.module-lib.removed-api.txt}",
-        ":framework-graphics{.module-lib.removed-api.txt}",
-        ":framework-media{.module-lib.removed-api.txt}",
-        ":framework-mediaprovider{.module-lib.removed-api.txt}",
-        ":framework-nearby{.module-lib.removed-api.txt}",
-        ":framework-permission{.module-lib.removed-api.txt}",
-        ":framework-permission-s{.module-lib.removed-api.txt}",
-        ":framework-scheduling{.module-lib.removed-api.txt}",
-        ":framework-sdkextensions{.module-lib.removed-api.txt}",
-        ":framework-statsd{.module-lib.removed-api.txt}",
-        ":framework-supplementalprocess{.module-lib.removed-api.txt}",
-        ":framework-tethering{.module-lib.removed-api.txt}",
-        ":framework-uwb{.module-lib.removed-api.txt}",
-        ":framework-wifi{.module-lib.removed-api.txt}",
-        ":non-updatable-module-lib-removed.txt",
-    ],
-    out: ["module-lib-removed.txt"],
-    tools: ["metalava"],
-    cmd: metalava_cmd + "$(in) --api $(out)",
-    dists: [
-        {
-            targets: ["droidcore"],
-            dir: "api",
-            dest: "module-lib-removed.txt",
-        },
-        {
-            targets: ["sdk"],
-            dir: "apistubs/android/module-lib/api",
-            dest: "removed.txt",
-        },
-    ],
-}
-
-genrule {
     name: "combined-removed-dex",
     visibility: [
         "//frameworks/base/boot",
@@ -438,90 +206,3 @@
     out: ["combined-removed-dex.txt"],
     cmd: "$(location gen_combined_removed_dex.sh) $(location metalava) $(genDir) $(in) > $(out)",
 }
-
-genrule {
-    name: "frameworks-base-api-system-server-current.txt",
-    srcs: [
-        ":service-media-s{.system-server.api.txt}",
-        ":service-permission{.system-server.api.txt}",
-        ":non-updatable-system-server-current.txt",
-    ],
-    out: ["system-server-current.txt"],
-    tools: ["metalava"],
-    cmd: metalava_cmd + "$(in) --api $(out)",
-    dists: [
-        {
-            targets: ["droidcore"],
-            dir: "api",
-            dest: "system-server-current.txt",
-        },
-        {
-            targets: ["sdk"],
-            dir: "apistubs/android/system-server/api",
-            dest: "android.txt",
-        },
-    ],
-}
-
-genrule {
-    name: "frameworks-base-api-system-server-removed.txt",
-    srcs: [
-        ":service-media-s{.system-server.removed-api.txt}",
-        ":service-permission{.system-server.removed-api.txt}",
-        ":non-updatable-system-server-removed.txt",
-    ],
-    out: ["system-server-removed.txt"],
-    tools: ["metalava"],
-    cmd: metalava_cmd + "$(in) --api $(out)",
-    dists: [
-        {
-            targets: ["droidcore"],
-            dir: "api",
-            dest: "system-server-removed.txt",
-        },
-        {
-            targets: ["sdk"],
-            dir: "apistubs/android/system-server/api",
-            dest: "removed.txt",
-        },
-    ],
-}
-
-// This rule will filter classes present in the jar files of mainline modules
-// from the lint database in api-versions.xml.
-// This is done to reduce the number of false positive NewApi findings in
-// java libraries that compile against the module SDK
-genrule {
-    name: "api-versions-xml-public-filtered",
-    srcs: [
-        // Note: order matters: first parameter is the full api-versions.xml
-        // after that the stubs files in any order
-        // stubs files are all modules that export API surfaces EXCEPT ART
-        ":framework-doc-stubs{.api_versions.xml}",
-        ":android.net.ipsec.ike.stubs{.jar}",
-        ":conscrypt.module.public.api.stubs{.jar}",
-        ":framework-appsearch.stubs{.jar}",
-        ":framework-connectivity.stubs{.jar}",
-        ":framework-connectivity-tiramisu.stubs{.jar}",
-        ":framework-graphics.stubs{.jar}",
-        ":framework-media.stubs{.jar}",
-        ":framework-mediaprovider.stubs{.jar}",
-        ":framework-nearby.stubs{.jar}",
-        ":framework-permission.stubs{.jar}",
-        ":framework-permission-s.stubs{.jar}",
-        ":framework-scheduling.stubs{.jar}",
-        ":framework-sdkextensions.stubs{.jar}",
-        ":framework-statsd.stubs{.jar}",
-        ":framework-supplementalprocess.stubs{.jar}",
-        ":framework-tethering.stubs{.jar}",
-        ":framework-uwb.stubs{.jar}",
-        ":framework-wifi.stubs{.jar}",
-        ":i18n.module.public.api.stubs{.jar}",
-    ],
-    out: ["api-versions-public-filtered.xml"],
-    tools: ["api_versions_trimmer"],
-    cmd: "$(location api_versions_trimmer) $(out) $(in)",
-    dist: {
-        targets: ["sdk"],
-    },
-}
diff --git a/api/api.go b/api/api.go
new file mode 100644
index 0000000..6cc129a
--- /dev/null
+++ b/api/api.go
@@ -0,0 +1,253 @@
+// 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 api
+
+import (
+	"sort"
+
+	"github.com/google/blueprint/proptools"
+
+	"android/soong/android"
+	"android/soong/genrule"
+)
+
+// The intention behind this soong plugin is to generate a number of "merged"
+// API-related modules that would otherwise require a large amount of very
+// similar Android.bp boilerplate to define. For example, the merged current.txt
+// API definitions (created by merging the non-updatable current.txt with all
+// the module current.txts). This simplifies the addition of new android
+// modules, by reducing the number of genrules etc a new module must be added to.
+
+// The properties of the combined_apis module type.
+type CombinedApisProperties struct {
+	// Module libraries in the bootclasspath
+	Bootclasspath []string
+	// Module libraries on the bootclasspath if include_nonpublic_framework_api is true.
+	Conditional_bootclasspath []string
+	// Module libraries in system server
+	System_server_classpath []string
+}
+
+type CombinedApis struct {
+	android.ModuleBase
+
+	properties CombinedApisProperties
+}
+
+func init() {
+	registerBuildComponents(android.InitRegistrationContext)
+}
+
+func registerBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("combined_apis", combinedApisModuleFactory)
+}
+
+var PrepareForCombinedApisTest = android.FixtureRegisterWithContext(registerBuildComponents)
+
+func (a *CombinedApis) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+}
+
+type genruleProps struct {
+	Name       *string
+	Cmd        *string
+	Dists      []android.Dist
+	Out        []string
+	Srcs       []string
+	Tools      []string
+	Visibility []string
+}
+
+// Struct to pass parameters for the various merged [current|removed].txt file modules we create.
+type MergedTxtDefinition struct {
+	// "current.txt" or "removed.txt"
+	TxtFilename string
+	// The module for the non-updatable / non-module part of the api.
+	BaseTxt string
+	// The list of modules that are relevant for this merged txt.
+	Modules []string
+	// The output tag for each module to use.e.g. {.public.api.txt} for current.txt
+	ModuleTag string
+	// public, system, module-lib or system-server
+	Scope string
+}
+
+func createMergedTxt(ctx android.LoadHookContext, txt MergedTxtDefinition) {
+	metalavaCmd := "$(location metalava)"
+	// Silence reflection warnings. See b/168689341
+	metalavaCmd += " -J--add-opens=java.base/java.util=ALL-UNNAMED "
+	metalavaCmd += " --quiet --no-banner --format=v2 "
+
+	filename := txt.TxtFilename
+	if txt.Scope != "public" {
+		filename = txt.Scope + "-" + filename
+	}
+
+	props := genruleProps{}
+	props.Name = proptools.StringPtr(ctx.ModuleName() + "-" + filename)
+	props.Tools = []string{"metalava"}
+	props.Out = []string{filename}
+	props.Cmd = proptools.StringPtr(metalavaCmd + "$(in) --api $(out)")
+	props.Srcs = createSrcs(txt.BaseTxt, txt.Modules, txt.ModuleTag)
+	props.Dists = []android.Dist{
+		{
+			Targets: []string{"droidcore"},
+			Dir:     proptools.StringPtr("api"),
+			Dest:    proptools.StringPtr(filename),
+		},
+		{
+			Targets: []string{"sdk"},
+			Dir:     proptools.StringPtr("apistubs/android/" + txt.Scope + "/api"),
+			Dest:    proptools.StringPtr(txt.TxtFilename),
+		},
+	}
+	props.Visibility = []string{"//visibility:public"}
+	ctx.CreateModule(genrule.GenRuleFactory, &props)
+}
+
+func createMergedStubsSrcjar(ctx android.LoadHookContext, modules []string) {
+	props := genruleProps{}
+	props.Name = proptools.StringPtr(ctx.ModuleName() + "-current.srcjar")
+	props.Tools = []string{"merge_zips"}
+	props.Out = []string{"current.srcjar"}
+	props.Cmd = proptools.StringPtr("$(location merge_zips) $(out) $(in)")
+	props.Srcs = createSrcs(":api-stubs-docs-non-updatable", modules, "{.public.stubs.source}")
+	props.Visibility = []string{"//visibility:private"} // Used by make module in //development, mind
+	ctx.CreateModule(genrule.GenRuleFactory, &props)
+}
+
+// This produces the same annotations.zip as framework-doc-stubs, but by using
+// outputs from individual modules instead of all the source code.
+func createMergedAnnotations(ctx android.LoadHookContext, modules []string) {
+	props := genruleProps{}
+	props.Name = proptools.StringPtr("sdk-annotations.zip")
+	props.Tools = []string{"merge_annotation_zips", "soong_zip"}
+	props.Out = []string{"annotations.zip"}
+	props.Cmd = proptools.StringPtr("$(location merge_annotation_zips) $(genDir)/out $(in) && " +
+		"$(location soong_zip) -o $(out) -C $(genDir)/out -D $(genDir)/out")
+	props.Srcs = createSrcs(":android-non-updatable-doc-stubs{.annotations.zip}", modules, "{.public.annotations.zip}")
+	ctx.CreateModule(genrule.GenRuleFactory, &props)
+}
+
+func createFilteredApiVersions(ctx android.LoadHookContext, modules []string) {
+	props := genruleProps{}
+	props.Name = proptools.StringPtr("api-versions-xml-public-filtered")
+	props.Tools = []string{"api_versions_trimmer"}
+	props.Out = []string{"api-versions-public-filtered.xml"}
+	props.Cmd = proptools.StringPtr("$(location api_versions_trimmer) $(out) $(in)")
+	// Note: order matters: first parameter is the full api-versions.xml
+	// after that the stubs files in any order
+	// stubs files are all modules that export API surfaces EXCEPT ART
+	props.Srcs = createSrcs(":framework-doc-stubs{.api_versions.xml}", modules, ".stubs{.jar}")
+	props.Dists = []android.Dist{{Targets: []string{"sdk"}}}
+	ctx.CreateModule(genrule.GenRuleFactory, &props)
+}
+
+func createSrcs(base string, modules []string, tag string) []string {
+	a := make([]string, 0, len(modules)+1)
+	a = append(a, base)
+	for _, module := range modules {
+		a = append(a, ":"+module+tag)
+	}
+	return a
+}
+
+func remove(s []string, v string) []string {
+	s2 := make([]string, 0, len(s))
+	for _, sv := range s {
+		if sv != v {
+			s2 = append(s2, sv)
+		}
+	}
+	return s2
+}
+
+func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) {
+	var textFiles []MergedTxtDefinition
+	// Two module libraries currently do not support @SystemApi so only have the public scope.
+	bcpWithSystemApi := bootclasspath
+	bcpWithSystemApi = remove(bcpWithSystemApi, "conscrypt.module.public.api")
+	bcpWithSystemApi = remove(bcpWithSystemApi, "i18n.module.public.api")
+
+	tagSuffix := []string{".api.txt}", ".removed-api.txt}"}
+	for i, f := range []string{"current.txt", "removed.txt"} {
+		textFiles = append(textFiles, MergedTxtDefinition{
+			TxtFilename: f,
+			BaseTxt:     ":non-updatable-" + f,
+			Modules:     bootclasspath,
+			ModuleTag:   "{.public" + tagSuffix[i],
+			Scope:       "public",
+		})
+		textFiles = append(textFiles, MergedTxtDefinition{
+			TxtFilename: f,
+			BaseTxt:     ":non-updatable-system-" + f,
+			Modules:     bcpWithSystemApi,
+			ModuleTag:   "{.system" + tagSuffix[i],
+			Scope:       "system",
+		})
+		textFiles = append(textFiles, MergedTxtDefinition{
+			TxtFilename: f,
+			BaseTxt:     ":non-updatable-module-lib-" + f,
+			Modules:     bcpWithSystemApi,
+			ModuleTag:   "{.module-lib" + tagSuffix[i],
+			Scope:       "module-lib",
+		})
+		textFiles = append(textFiles, MergedTxtDefinition{
+			TxtFilename: f,
+			BaseTxt:     ":non-updatable-system-server-" + f,
+			Modules:     system_server_classpath,
+			ModuleTag:   "{.system-server" + tagSuffix[i],
+			Scope:       "system-server",
+		})
+	}
+	for _, txt := range textFiles {
+		createMergedTxt(ctx, txt)
+	}
+}
+
+func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) {
+	bootclasspath := a.properties.Bootclasspath
+	if ctx.Config().VendorConfig("ANDROID").Bool("include_nonpublic_framework_api") {
+		bootclasspath = append(bootclasspath, a.properties.Conditional_bootclasspath...)
+		sort.Strings(bootclasspath)
+	}
+	createMergedTxts(ctx, bootclasspath, a.properties.System_server_classpath)
+
+	createMergedStubsSrcjar(ctx, bootclasspath)
+
+	// Conscrypt and i18n currently do not enable annotations
+	annotationModules := bootclasspath
+	annotationModules = remove(annotationModules, "conscrypt.module.public.api")
+	annotationModules = remove(annotationModules, "i18n.module.public.api")
+	createMergedAnnotations(ctx, annotationModules)
+
+	// For the filtered api versions, we prune all APIs except art module's APIs. because
+	// 1) ART apis are available by default to all modules, while other module-to-module deps are
+	//    explicit and probably receive more scrutiny anyway
+	// 2) The number of ART/libcore APIs is large, so not linting them would create a large gap
+	// 3) It's a compromise. Ideally we wouldn't be filtering out any module APIs, and have
+	//    per-module lint databases that excludes just that module's APIs. Alas, that's more
+	//    difficult to achieve.
+	filteredModules := bootclasspath
+	filteredModules = remove(filteredModules, "art.module.public.api")
+	createFilteredApiVersions(ctx, filteredModules)
+}
+
+func combinedApisModuleFactory() android.Module {
+	module := &CombinedApis{}
+	module.AddProperties(&module.properties)
+	android.InitAndroidModule(module)
+	android.AddLoadHook(module, func(ctx android.LoadHookContext) { module.createInternalModules(ctx) })
+	return module
+}
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index 13bf197..fbb99d2 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -43,7 +43,7 @@
 
 #define DEFAULT_DELAY_NS (1000000000LL)
 
-#define DEFAULT_BYTES_SIZE_LIMIT (96 * 1024 * 1024)        // 96MB
+#define DEFAULT_BYTES_SIZE_LIMIT (400 * 1024 * 1024)        // 400MB
 #define DEFAULT_REFACTORY_PERIOD_MS (24 * 60 * 60 * 1000)  // 1 Day
 
 // Skip these sections (for dumpstate only)
diff --git a/cmds/incidentd/src/WorkDirectory.cpp b/cmds/incidentd/src/WorkDirectory.cpp
index 23d80d7..dd33fdf 100644
--- a/cmds/incidentd/src/WorkDirectory.cpp
+++ b/cmds/incidentd/src/WorkDirectory.cpp
@@ -533,7 +533,7 @@
 WorkDirectory::WorkDirectory()
         :mDirectory("/data/misc/incidents"),
          mMaxFileCount(100),
-         mMaxDiskUsageBytes(100 * 1024 * 1024) {  // Incident reports can take up to 100MB on disk.
+         mMaxDiskUsageBytes(400 * 1024 * 1024) {  // Incident reports can take up to 400MB on disk.
                                                  // TODO: Should be a flag.
     create_directory(mDirectory.c_str());
 }
diff --git a/core/api/current.txt b/core/api/current.txt
index 7d3d57d..4f15fa9 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9743,13 +9743,14 @@
     field public static final int ERROR_BLUETOOTH_NOT_ALLOWED = 2; // 0x2
     field public static final int ERROR_BLUETOOTH_NOT_ENABLED = 1; // 0x1
     field public static final int ERROR_DEVICE_NOT_BONDED = 3; // 0x3
-    field public static final int ERROR_FEATURE_NOT_SUPPORTED = 10; // 0xa
-    field public static final int ERROR_GATT_WRITE_NOT_ALLOWED = 101; // 0x65
-    field public static final int ERROR_GATT_WRITE_REQUEST_BUSY = 102; // 0x66
+    field public static final int ERROR_GATT_WRITE_NOT_ALLOWED = 200; // 0xc8
+    field public static final int ERROR_GATT_WRITE_REQUEST_BUSY = 201; // 0xc9
     field public static final int ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION = 6; // 0x6
     field public static final int ERROR_MISSING_BLUETOOTH_PRIVILEGED_PERMISSION = 8; // 0x8
     field public static final int ERROR_PROFILE_SERVICE_NOT_BOUND = 9; // 0x9
     field public static final int ERROR_UNKNOWN = 2147483647; // 0x7fffffff
+    field public static final int FEATURE_NOT_SUPPORTED = 11; // 0xb
+    field public static final int FEATURE_SUPPORTED = 10; // 0xa
     field public static final int SUCCESS = 0; // 0x0
   }
 
@@ -10876,6 +10877,8 @@
     method @Deprecated @RequiresPermission(allOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.BROADCAST_STICKY}) public abstract void removeStickyBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle);
     method public abstract void revokeUriPermission(android.net.Uri, int);
     method public abstract void revokeUriPermission(String, android.net.Uri, int);
+    method public void selfRevokePermission(@NonNull String);
+    method public void selfRevokePermissions(@NonNull java.util.Collection<java.lang.String>);
     method public abstract void sendBroadcast(@RequiresPermission android.content.Intent);
     method public abstract void sendBroadcast(@RequiresPermission android.content.Intent, @Nullable String);
     method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle);
@@ -12762,11 +12765,16 @@
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.SessionInfo> CREATOR;
     field public static final int INVALID_ID = -1; // 0xffffffff
-    field public static final int STAGED_SESSION_ACTIVATION_FAILED = 2; // 0x2
-    field public static final int STAGED_SESSION_CONFLICT = 4; // 0x4
-    field public static final int STAGED_SESSION_NO_ERROR = 0; // 0x0
-    field public static final int STAGED_SESSION_UNKNOWN = 3; // 0x3
-    field public static final int STAGED_SESSION_VERIFICATION_FAILED = 1; // 0x1
+    field public static final int SESSION_ACTIVATION_FAILED = 2; // 0x2
+    field public static final int SESSION_CONFLICT = 4; // 0x4
+    field public static final int SESSION_NO_ERROR = 0; // 0x0
+    field public static final int SESSION_UNKNOWN_ERROR = 3; // 0x3
+    field public static final int SESSION_VERIFICATION_FAILED = 1; // 0x1
+    field @Deprecated public static final int STAGED_SESSION_ACTIVATION_FAILED = 2; // 0x2
+    field @Deprecated public static final int STAGED_SESSION_CONFLICT = 4; // 0x4
+    field @Deprecated public static final int STAGED_SESSION_NO_ERROR = 0; // 0x0
+    field @Deprecated public static final int STAGED_SESSION_UNKNOWN = 3; // 0x3
+    field @Deprecated public static final int STAGED_SESSION_VERIFICATION_FAILED = 1; // 0x1
   }
 
   public static class PackageInstaller.SessionParams implements android.os.Parcelable {
@@ -15978,6 +15986,8 @@
     method public String getFontFeatureSettings();
     method public float getFontMetrics(android.graphics.Paint.FontMetrics);
     method public android.graphics.Paint.FontMetrics getFontMetrics();
+    method public void getFontMetricsInt(@NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, boolean, @NonNull android.graphics.Paint.FontMetricsInt);
+    method public void getFontMetricsInt(@NonNull char[], @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, boolean, @NonNull android.graphics.Paint.FontMetricsInt);
     method public int getFontMetricsInt(android.graphics.Paint.FontMetricsInt);
     method public android.graphics.Paint.FontMetricsInt getFontMetricsInt();
     method public float getFontSpacing();
@@ -21896,16 +21906,29 @@
     method public android.media.Image acquireNextImage();
     method public void close();
     method public void discardFreeBuffers();
+    method public long getDataSpace();
+    method public int getHardwareBufferFormat();
     method public int getHeight();
     method public int getImageFormat();
     method public int getMaxImages();
     method public android.view.Surface getSurface();
+    method public long getUsage();
     method public int getWidth();
     method @NonNull public static android.media.ImageReader newInstance(@IntRange(from=1) int, @IntRange(from=1) int, int, @IntRange(from=1) int);
     method @NonNull public static android.media.ImageReader newInstance(@IntRange(from=1) int, @IntRange(from=1) int, int, @IntRange(from=1) int, long);
     method public void setOnImageAvailableListener(android.media.ImageReader.OnImageAvailableListener, android.os.Handler);
   }
 
+  public static final class ImageReader.Builder {
+    ctor public ImageReader.Builder(@IntRange(from=1) int, @IntRange(from=1) int);
+    method @NonNull public android.media.ImageReader build();
+    method @NonNull public android.media.ImageReader.Builder setDefaultDataSpace(long);
+    method @NonNull public android.media.ImageReader.Builder setDefaultHardwareBufferFormat(int);
+    method @NonNull public android.media.ImageReader.Builder setImageFormat(int);
+    method @NonNull public android.media.ImageReader.Builder setMaxImages(int);
+    method @NonNull public android.media.ImageReader.Builder setUsage(long);
+  }
+
   public static interface ImageReader.OnImageAvailableListener {
     method public void onImageAvailable(android.media.ImageReader);
   }
@@ -27366,11 +27389,13 @@
     method @NonNull public android.net.VpnService.Builder addDnsServer(@NonNull java.net.InetAddress);
     method @NonNull public android.net.VpnService.Builder addDnsServer(@NonNull String);
     method @NonNull public android.net.VpnService.Builder addRoute(@NonNull java.net.InetAddress, int);
+    method @NonNull public android.net.VpnService.Builder addRoute(@NonNull android.net.IpPrefix);
     method @NonNull public android.net.VpnService.Builder addRoute(@NonNull String, int);
     method @NonNull public android.net.VpnService.Builder addSearchDomain(@NonNull String);
     method @NonNull public android.net.VpnService.Builder allowBypass();
     method @NonNull public android.net.VpnService.Builder allowFamily(int);
     method @Nullable public android.os.ParcelFileDescriptor establish();
+    method @NonNull public android.net.VpnService.Builder excludeRoute(@NonNull android.net.IpPrefix);
     method @NonNull public android.net.VpnService.Builder setBlocking(boolean);
     method @NonNull public android.net.VpnService.Builder setConfigureIntent(@NonNull android.app.PendingIntent);
     method @NonNull public android.net.VpnService.Builder setHttpProxy(@NonNull android.net.ProxyInfo);
@@ -27688,6 +27713,23 @@
 
 package android.net.vcn {
 
+  public final class VcnCellUnderlyingNetworkTemplate extends android.net.vcn.VcnUnderlyingNetworkTemplate {
+    method @NonNull public java.util.Set<java.lang.String> getOperatorPlmnIds();
+    method public int getOpportunistic();
+    method public int getRoaming();
+    method @NonNull public java.util.Set<java.lang.Integer> getSimSpecificCarrierIds();
+  }
+
+  public static final class VcnCellUnderlyingNetworkTemplate.Builder {
+    ctor public VcnCellUnderlyingNetworkTemplate.Builder();
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate build();
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMetered(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOperatorPlmnIds(@NonNull java.util.Set<java.lang.String>);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOpportunistic(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setRoaming(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setSimSpecificCarrierIds(@NonNull java.util.Set<java.lang.Integer>);
+  }
+
   public final class VcnConfig implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public java.util.Set<android.net.vcn.VcnGatewayConnectionConfig> getGatewayConnectionConfigs();
@@ -27706,6 +27748,7 @@
     method @NonNull public String getGatewayConnectionName();
     method @IntRange(from=0x500) public int getMaxMtu();
     method @NonNull public long[] getRetryIntervalsMillis();
+    method @NonNull public java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities();
   }
 
   public static final class VcnGatewayConnectionConfig.Builder {
@@ -27715,6 +27758,7 @@
     method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int);
     method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=0x500) int);
     method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryIntervalsMillis(@NonNull long[]);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setVcnUnderlyingNetworkPriorities(@NonNull java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate>);
   }
 
   public class VcnManager {
@@ -27738,6 +27782,24 @@
     method public abstract void onStatusChanged(int);
   }
 
+  public abstract class VcnUnderlyingNetworkTemplate {
+    method public int getMetered();
+    field public static final int MATCH_ANY = 0; // 0x0
+    field public static final int MATCH_FORBIDDEN = 2; // 0x2
+    field public static final int MATCH_REQUIRED = 1; // 0x1
+  }
+
+  public final class VcnWifiUnderlyingNetworkTemplate extends android.net.vcn.VcnUnderlyingNetworkTemplate {
+    method @NonNull public java.util.Set<java.lang.String> getSsids();
+  }
+
+  public static final class VcnWifiUnderlyingNetworkTemplate.Builder {
+    ctor public VcnWifiUnderlyingNetworkTemplate.Builder();
+    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate build();
+    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMetered(int);
+    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setSsids(@NonNull java.util.Set<java.lang.String>);
+  }
+
 }
 
 package android.nfc {
@@ -40270,6 +40332,7 @@
     field public static final int PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL = 2048; // 0x800
     field public static final int PROPERTY_RTT = 1024; // 0x400
     field public static final int PROPERTY_SELF_MANAGED = 256; // 0x100
+    field public static final int PROPERTY_TETHERED_CALL = 32768; // 0x8000
     field public static final int PROPERTY_VOIP_AUDIO_MODE = 4096; // 0x1000
     field public static final int PROPERTY_WIFI = 8; // 0x8
   }
@@ -40298,6 +40361,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.telecom.CallAudioState> CREATOR;
     field public static final int ROUTE_BLUETOOTH = 2; // 0x2
     field public static final int ROUTE_EARPIECE = 1; // 0x1
+    field public static final int ROUTE_EXTERNAL = 16; // 0x10
     field public static final int ROUTE_SPEAKER = 8; // 0x8
     field public static final int ROUTE_WIRED_HEADSET = 4; // 0x4
     field public static final int ROUTE_WIRED_OR_EARPIECE = 5; // 0x5
@@ -40572,6 +40636,7 @@
     field public static final int PROPERTY_IS_RTT = 256; // 0x100
     field public static final int PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL = 1024; // 0x400
     field public static final int PROPERTY_SELF_MANAGED = 128; // 0x80
+    field public static final int PROPERTY_TETHERED_CALL = 16384; // 0x4000
     field public static final int PROPERTY_WIFI = 8; // 0x8
     field public static final int STATE_ACTIVE = 4; // 0x4
     field public static final int STATE_DIALING = 3; // 0x3
@@ -41938,6 +42003,7 @@
     field public static final int EPDG_ADDRESS_PCO = 2; // 0x2
     field public static final int EPDG_ADDRESS_PLMN = 1; // 0x1
     field public static final int EPDG_ADDRESS_STATIC = 0; // 0x0
+    field public static final int EPDG_ADDRESS_VISITED_COUNTRY = 4; // 0x4
     field public static final int ID_TYPE_FQDN = 2; // 0x2
     field public static final int ID_TYPE_KEY_ID = 11; // 0xb
     field public static final int ID_TYPE_RFC822_ADDR = 3; // 0x3
@@ -44900,6 +44966,7 @@
   public class BoringLayout extends android.text.Layout implements android.text.TextUtils.EllipsizeCallback {
     ctor public BoringLayout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean);
     ctor public BoringLayout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int);
+    ctor public BoringLayout(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, float, float, @NonNull android.text.BoringLayout.Metrics, boolean, @NonNull android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean);
     method public void ellipsized(int, int);
     method public int getBottomPadding();
     method public int getEllipsisCount(int);
@@ -44914,9 +44981,12 @@
     method public int getTopPadding();
     method public static android.text.BoringLayout.Metrics isBoring(CharSequence, android.text.TextPaint);
     method public static android.text.BoringLayout.Metrics isBoring(CharSequence, android.text.TextPaint, android.text.BoringLayout.Metrics);
+    method @Nullable public static android.text.BoringLayout.Metrics isBoring(@NonNull CharSequence, @NonNull android.text.TextPaint, @NonNull android.text.TextDirectionHeuristic, boolean, @Nullable android.text.BoringLayout.Metrics);
     method public static android.text.BoringLayout make(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean);
     method public static android.text.BoringLayout make(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int);
+    method @NonNull public static android.text.BoringLayout make(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, @NonNull android.text.BoringLayout.Metrics, boolean, @NonNull android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean);
     method public android.text.BoringLayout replaceOrMake(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean);
+    method @NonNull public android.text.BoringLayout replaceOrMake(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, @NonNull android.text.BoringLayout.Metrics, boolean, @NonNull android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean);
     method public android.text.BoringLayout replaceOrMake(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int);
   }
 
@@ -45125,6 +45195,7 @@
     method public abstract int getTopPadding();
     method public final int getWidth();
     method public final void increaseWidthTo(int);
+    method public boolean isFallbackLineSpacingEnabled();
     method public boolean isRtlCharAt(int);
     method protected final boolean isSpanned();
     field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 53bc8a6..4d84537 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -88,6 +88,14 @@
     field public static final String TEST_NETWORK_SERVICE = "test_network";
   }
 
+  public class Intent implements java.lang.Cloneable android.os.Parcelable {
+    field public static final String ACTION_SETTING_RESTORED = "android.os.action.SETTING_RESTORED";
+    field public static final String EXTRA_SETTING_NAME = "setting_name";
+    field public static final String EXTRA_SETTING_NEW_VALUE = "new_value";
+    field public static final String EXTRA_SETTING_PREVIOUS_VALUE = "previous_value";
+    field public static final String EXTRA_SETTING_RESTORED_FROM_SDK_INT = "restored_from_sdk_int";
+  }
+
 }
 
 package android.content.pm {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index e1442a8..295d386 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -149,6 +149,7 @@
     field public static final String MANAGE_APP_PREDICTIONS = "android.permission.MANAGE_APP_PREDICTIONS";
     field public static final String MANAGE_APP_TOKENS = "android.permission.MANAGE_APP_TOKENS";
     field public static final String MANAGE_AUTO_FILL = "android.permission.MANAGE_AUTO_FILL";
+    field public static final String MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED = "android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED";
     field public static final String MANAGE_CARRIER_OEM_UNLOCK_STATE = "android.permission.MANAGE_CARRIER_OEM_UNLOCK_STATE";
     field public static final String MANAGE_CA_CERTIFICATES = "android.permission.MANAGE_CA_CERTIFICATES";
     field public static final String MANAGE_CONTENT_CAPTURE = "android.permission.MANAGE_CONTENT_CAPTURE";
@@ -165,6 +166,7 @@
     field public static final String MANAGE_ROLE_HOLDERS = "android.permission.MANAGE_ROLE_HOLDERS";
     field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
     field public static final String MANAGE_ROTATION_RESOLVER = "android.permission.MANAGE_ROTATION_RESOLVER";
+    field public static final String MANAGE_SAFETY_CENTER = "android.permission.MANAGE_SAFETY_CENTER";
     field public static final String MANAGE_SEARCH_UI = "android.permission.MANAGE_SEARCH_UI";
     field public static final String MANAGE_SENSOR_PRIVACY = "android.permission.MANAGE_SENSOR_PRIVACY";
     field public static final String MANAGE_SMARTSPACE = "android.permission.MANAGE_SMARTSPACE";
@@ -177,6 +179,7 @@
     field public static final String MANAGE_USB = "android.permission.MANAGE_USB";
     field public static final String MANAGE_USERS = "android.permission.MANAGE_USERS";
     field public static final String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission.MANAGE_USER_OEM_UNLOCK_STATE";
+    field public static final String MANAGE_WEAK_ESCROW_TOKEN = "android.permission.MANAGE_WEAK_ESCROW_TOKEN";
     field public static final String MANAGE_WIFI_AUTO_JOIN = "android.permission.MANAGE_WIFI_AUTO_JOIN";
     field public static final String MANAGE_WIFI_COUNTRY_CODE = "android.permission.MANAGE_WIFI_COUNTRY_CODE";
     field public static final String MARK_DEVICE_ORGANIZATION_OWNED = "android.permission.MARK_DEVICE_ORGANIZATION_OWNED";
@@ -297,6 +300,7 @@
     field public static final String SIGNAL_REBOOT_READINESS = "android.permission.SIGNAL_REBOOT_READINESS";
     field public static final String SOUND_TRIGGER_RUN_IN_BATTERY_SAVER = "android.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER";
     field public static final String START_ACTIVITIES_FROM_BACKGROUND = "android.permission.START_ACTIVITIES_FROM_BACKGROUND";
+    field public static final String START_CROSS_PROFILE_ACTIVITIES = "android.permission.START_CROSS_PROFILE_ACTIVITIES";
     field public static final String START_REVIEW_PERMISSION_DECISIONS = "android.permission.START_REVIEW_PERMISSION_DECISIONS";
     field public static final String STATUS_BAR_SERVICE = "android.permission.STATUS_BAR_SERVICE";
     field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES";
@@ -403,6 +407,7 @@
     field public static final int config_systemAmbientAudioIntelligence = 17039411; // 0x1040033
     field public static final int config_systemAppProtectionService;
     field public static final int config_systemAudioIntelligence = 17039412; // 0x1040034
+    field public static final int config_systemAutomotiveCalendarSyncManager;
     field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
     field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029
     field public static final int config_systemCompanionDeviceProvider = 17039417; // 0x1040039
@@ -767,18 +772,32 @@
   }
 
   public class KeyguardManager {
+    method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public long addWeakEscrowToken(@NonNull byte[], @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull android.app.KeyguardManager.WeakEscrowTokenActivatedListener);
     method public android.content.Intent createConfirmFactoryResetCredentialIntent(CharSequence, CharSequence, CharSequence);
     method @RequiresPermission("android.permission.SET_INITIAL_LOCK") public int getMinLockLength(boolean, int);
     method @RequiresPermission(android.Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS) public boolean getPrivateNotificationsAllowed();
     method @RequiresPermission("android.permission.SET_INITIAL_LOCK") public boolean isValidLockPasswordComplexity(int, @NonNull byte[], int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean isWeakEscrowTokenActive(long, @NonNull android.os.UserHandle);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean isWeakEscrowTokenValid(long, @NonNull byte[], @NonNull android.os.UserHandle);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean registerWeakEscrowTokenRemovedListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.KeyguardManager.WeakEscrowTokenRemovedListener);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean removeWeakEscrowToken(long, @NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.SHOW_KEYGUARD_MESSAGE) public void requestDismissKeyguard(@NonNull android.app.Activity, @Nullable CharSequence, @Nullable android.app.KeyguardManager.KeyguardDismissCallback);
     method @RequiresPermission("android.permission.SET_INITIAL_LOCK") public boolean setLock(int, @NonNull byte[], int);
     method @RequiresPermission(android.Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS) public void setPrivateNotificationsAllowed(boolean);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean unregisterWeakEscrowTokenRemovedListener(@NonNull android.app.KeyguardManager.WeakEscrowTokenRemovedListener);
     field public static final int PASSWORD = 0; // 0x0
     field public static final int PATTERN = 2; // 0x2
     field public static final int PIN = 1; // 0x1
   }
 
+  public static interface KeyguardManager.WeakEscrowTokenActivatedListener {
+    method public void onWeakEscrowTokenActivated(long, @NonNull android.os.UserHandle);
+  }
+
+  public static interface KeyguardManager.WeakEscrowTokenRemovedListener {
+    method public void onWeakEscrowTokenRemoved(long, @NonNull android.os.UserHandle);
+  }
+
   public class LocaleManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_APP_SPECIFIC_LOCALES) public android.os.LocaleList getApplicationLocales(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void setApplicationLocales(@NonNull String, @NonNull android.os.LocaleList);
@@ -1019,12 +1038,12 @@
     field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE";
     field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
     field @RequiresPermission(android.Manifest.permission.MANAGE_FACTORY_RESET_PROTECTION) public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED = "android.app.action.RESET_PROTECTION_POLICY_CHANGED";
-    field public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION = "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION";
-    field public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
-    field public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_PROFILE";
+    field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION = "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION";
+    field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
+    field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_PROFILE";
     field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
     field @Deprecated public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
-    field public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER";
+    field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER";
     field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6
     field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb
     field public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd
@@ -1045,6 +1064,7 @@
     field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL";
     field public static final String EXTRA_PROVISIONING_ORGANIZATION_NAME = "android.app.extra.PROVISIONING_ORGANIZATION_NAME";
     field public static final String EXTRA_PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE = "android.app.extra.PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE";
+    field public static final String EXTRA_PROVISIONING_ROLE_HOLDER_CUSTOM_USER_CONSENT_INTENT = "android.app.extra.PROVISIONING_ROLE_HOLDER_CUSTOM_USER_CONSENT_INTENT";
     field public static final String EXTRA_PROVISIONING_SKIP_OWNERSHIP_DISCLAIMER = "android.app.extra.PROVISIONING_SKIP_OWNERSHIP_DISCLAIMER";
     field public static final String EXTRA_PROVISIONING_SUPPORTED_MODES = "android.app.extra.PROVISIONING_SUPPORTED_MODES";
     field public static final String EXTRA_PROVISIONING_SUPPORT_URL = "android.app.extra.PROVISIONING_SUPPORT_URL";
@@ -2218,9 +2238,12 @@
 
   public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile {
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean connect(android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int connectAudio();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnect(android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int disconnectAudio();
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getAudioState(@NonNull android.bluetooth.BluetoothDevice);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isInbandRingingEnabled();
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean isInbandRingingEnabled();
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean startScoUsingVirtualVoiceCall();
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean stopScoUsingVirtualVoiceCall();
@@ -2299,6 +2322,14 @@
 
   public final class BluetoothStatusCodes {
     field public static final int ERROR_ANOTHER_ACTIVE_OOB_REQUEST = 1000; // 0x3e8
+    field public static final int ERROR_AUDIO_DEVICE_ALREADY_CONNECTED = 1116; // 0x45c
+    field public static final int ERROR_AUDIO_DEVICE_ALREADY_DISCONNECTED = 1117; // 0x45d
+    field public static final int ERROR_AUDIO_ROUTE_BLOCKED = 1118; // 0x45e
+    field public static final int ERROR_CALL_ACTIVE = 1119; // 0x45f
+    field public static final int ERROR_NOT_ACTIVE_DEVICE = 12; // 0xc
+    field public static final int ERROR_NO_ACTIVE_DEVICES = 13; // 0xd
+    field public static final int ERROR_PROFILE_NOT_CONNECTED = 14; // 0xe
+    field public static final int ERROR_TIMEOUT = 15; // 0xf
   }
 
   public final class BluetoothUuid {
@@ -2428,6 +2459,25 @@
 
 package android.bluetooth.le {
 
+  public final class AdvertiseSettings implements android.os.Parcelable {
+    method public int getOwnAddressType();
+  }
+
+  public static final class AdvertiseSettings.Builder {
+    method @NonNull public android.bluetooth.le.AdvertiseSettings.Builder setOwnAddressType(int);
+  }
+
+  public final class AdvertisingSetParameters implements android.os.Parcelable {
+    method public int getOwnAddressType();
+    field public static final int ADDRESS_TYPE_DEFAULT = -1; // 0xffffffff
+    field public static final int ADDRESS_TYPE_PUBLIC = 0; // 0x0
+    field public static final int ADDRESS_TYPE_RANDOM = 1; // 0x1
+  }
+
+  public static final class AdvertisingSetParameters.Builder {
+    method @NonNull public android.bluetooth.le.AdvertisingSetParameters.Builder setOwnAddressType(int);
+  }
+
   public final class BluetoothLeScanner {
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.UPDATE_DEVICE_STATS}) public void startScanFromSource(android.os.WorkSource, android.bluetooth.le.ScanCallback);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.UPDATE_DEVICE_STATS}) public void startScanFromSource(java.util.List<android.bluetooth.le.ScanFilter>, android.bluetooth.le.ScanSettings, android.os.WorkSource, android.bluetooth.le.ScanCallback);
@@ -2810,7 +2860,7 @@
   }
 
   public class CrossProfileApps {
-    method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES) public void startActivity(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
+    method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_PROFILES, android.Manifest.permission.START_CROSS_PROFILE_ACTIVITIES}) public void startActivity(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
   }
 
   public class DataLoaderParams {
@@ -4119,7 +4169,7 @@
   public class ContextHubClient implements java.io.Closeable {
     method public void close();
     method @NonNull public android.hardware.location.ContextHubInfo getAttachedHub();
-    method public int getId();
+    method @IntRange(from=0, to=65535) public int getId();
     method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int sendMessageToNanoApp(@NonNull android.hardware.location.NanoAppMessage);
   }
 
@@ -6316,6 +6366,7 @@
     method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public boolean captureFrame(String, android.view.Surface, android.media.tv.TvStreamConfig);
     method @NonNull public java.util.List<java.lang.String> getAvailableExtensionInterfaceNames(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public java.util.List<android.media.tv.TvStreamConfig> getAvailableTvStreamConfigList(String);
+    method public int getClientPriority(int, @Nullable String);
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TUNED_INFO) public java.util.List<android.media.tv.TunedInfo> getCurrentTunedInfos();
     method @NonNull @RequiresPermission("android.permission.DVB_DEVICE") public java.util.List<android.media.tv.DvbDeviceInfo> getDvbDeviceList();
     method @Nullable public android.os.IBinder getExtensionInterface(@NonNull String, @NonNull String);
@@ -6333,6 +6384,7 @@
 
   public static final class TvInputManager.Hardware {
     method public void overrideAudioSink(int, String, int, int, int);
+    method public void overrideAudioSink(@NonNull android.media.AudioDeviceInfo, @IntRange(from=0) int, int, int);
     method public void setStreamVolume(float);
     method public boolean setSurface(android.view.Surface, android.media.tv.TvStreamConfig);
   }
@@ -6435,7 +6487,9 @@
   }
 
   public class Lnb implements java.lang.AutoCloseable {
+    method public void addCallback(@NonNull android.media.tv.tuner.LnbCallback, @NonNull java.util.concurrent.Executor);
     method public void close();
+    method public boolean removeCallback(@NonNull android.media.tv.tuner.LnbCallback);
     method public int sendDiseqcMessage(@NonNull byte[]);
     method public int setSatellitePosition(int);
     method public int setTone(int);
@@ -6472,6 +6526,7 @@
     method public void clearOnTuneEventListener();
     method public void clearResourceLostListener();
     method public void close();
+    method public void closeFrontend();
     method public int connectCiCam(int);
     method public int connectFrontendToCiCam(int);
     method public int disconnectCiCam();
@@ -6500,6 +6555,7 @@
     method public void setOnTuneEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.OnTuneEventListener);
     method public void setResourceLostListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.Tuner.OnResourceLostListener);
     method public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner);
+    method public int transferOwner(@NonNull android.media.tv.tuner.Tuner);
     method public int tune(@NonNull android.media.tv.tuner.frontend.FrontendSettings);
     method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void updateResourcePriority(int, int);
     field public static final int INVALID_AV_SYNC_ID = -1; // 0xffffffff
@@ -6713,8 +6769,8 @@
     method @Nullable public String acquireSharedFilterToken();
     method public void close();
     method public int configure(@NonNull android.media.tv.tuner.filter.FilterConfiguration);
-    method public int delayCallbackUntilBufferFilled(int);
-    method public int delayCallbackUntilTimeMillis(long);
+    method public int delayCallbackUntilBytesAccumulated(int);
+    method public int delayCallbackUntilMillisElapsed(long);
     method public int flush();
     method public void freeSharedFilterToken(@NonNull String);
     method @Deprecated public int getId();
@@ -9582,6 +9638,7 @@
     method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
     method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable);
     method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>);
+    method @BinderThread public void onSelfRevokePermissions(@NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull Runnable);
     method @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @BinderThread public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull android.permission.AdminPermissionControlParams, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
@@ -10797,7 +10854,7 @@
 
   public class GameService extends android.app.Service {
     ctor public GameService();
-    method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent);
+    method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
     method public void onConnected();
     method public void onDisconnected();
     field public static final String ACTION_GAME_SERVICE = "android.service.games.action.GAME_SERVICE";
@@ -10812,7 +10869,7 @@
 
   public abstract class GameSessionService extends android.app.Service {
     ctor public GameSessionService();
-    method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent);
+    method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
     method @NonNull public abstract android.service.games.GameSession onNewSession(@NonNull android.service.games.CreateGameSessionRequest);
     field public static final String ACTION_GAME_SESSION_SERVICE = "android.service.games.action.GAME_SESSION_SERVICE";
   }
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 6586ae0..66250fce 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -36,6 +36,7 @@
     field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
     field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
     field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS";
+    field public static final String REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL = "android.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL";
     field public static final String SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS = "android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS";
     field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS";
     field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
@@ -66,6 +67,7 @@
   public static final class R.string {
     field public static final int config_defaultAssistant = 17039393; // 0x1040021
     field public static final int config_defaultDialer = 17039395; // 0x1040023
+    field public static final int config_systemAutomotiveCalendarSyncManager;
     field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
     field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029
     field public static final int config_systemGallery = 17039399; // 0x1040027
@@ -1829,6 +1831,14 @@
     method @NonNull public android.os.VibrationEffect build(int);
   }
 
+  public abstract class Vibrator {
+    method public int getDefaultVibrationIntensity(int);
+    field public static final int VIBRATION_INTENSITY_HIGH = 3; // 0x3
+    field public static final int VIBRATION_INTENSITY_LOW = 1; // 0x1
+    field public static final int VIBRATION_INTENSITY_MEDIUM = 2; // 0x2
+    field public static final int VIBRATION_INTENSITY_OFF = 0; // 0x0
+  }
+
   public class VintfObject {
     method public static String[] getHalNamesAndVersions();
     method @NonNull public static String getPlatformSepolicyVersion();
@@ -2036,6 +2046,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData();
     method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData(boolean);
     method @NonNull public android.content.AttributionSource registerAttributionSource(@NonNull android.content.AttributionSource);
+    method public void revokePostNotificationPermissionWithoutKillForTest(@NonNull String, int);
   }
 
 }
@@ -2309,6 +2320,7 @@
     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();
+    method public final boolean shouldShowComplications();
   }
 
 }
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
index 8e01779..3c9b232 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -172,7 +172,7 @@
     private AccessibilityGestureEvent(@NonNull Parcel parcel) {
         mGestureId = parcel.readInt();
         mDisplayId = parcel.readInt();
-        ParceledListSlice<MotionEvent> slice = parcel.readParcelable(getClass().getClassLoader(), android.content.pm.ParceledListSlice.class);
+        ParceledListSlice<MotionEvent> slice = parcel.readParcelable(getClass().getClassLoader());
         mMotionEvents = slice.getList();
     }
 
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 1167d0b..04c784e 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -1094,8 +1094,8 @@
         mInteractiveUiTimeout = parcel.readInt();
         flags = parcel.readInt();
         crashed = parcel.readInt() != 0;
-        mComponentName = parcel.readParcelable(this.getClass().getClassLoader(), android.content.ComponentName.class);
-        mResolveInfo = parcel.readParcelable(null, android.content.pm.ResolveInfo.class);
+        mComponentName = parcel.readParcelable(this.getClass().getClassLoader());
+        mResolveInfo = parcel.readParcelable(null);
         mSettingsActivityName = parcel.readString();
         mCapabilities = parcel.readInt();
         mSummaryResId = parcel.readInt();
diff --git a/core/java/android/accessibilityservice/GestureDescription.java b/core/java/android/accessibilityservice/GestureDescription.java
index a821dad..857c541 100644
--- a/core/java/android/accessibilityservice/GestureDescription.java
+++ b/core/java/android/accessibilityservice/GestureDescription.java
@@ -525,7 +525,7 @@
         public GestureStep(Parcel parcel) {
             timeSinceGestureStart = parcel.readLong();
             Parcelable[] parcelables =
-                    parcel.readParcelableArray(TouchPoint.class.getClassLoader());
+                    parcel.readParcelableArray(TouchPoint.class.getClassLoader(), TouchPoint.class);
             numTouchPoints = (parcelables == null) ? 0 : parcelables.length;
             touchPoints = new TouchPoint[numTouchPoints];
             for (int i = 0; i < numTouchPoints; i++) {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index a140983..9f8d246 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1903,7 +1903,7 @@
         public void readFromParcel(Parcel source) {
             id = source.readInt();
             persistentId = source.readInt();
-            childrenTaskInfos = source.readArrayList(RecentTaskInfo.class.getClassLoader(), android.app.ActivityManager.RecentTaskInfo.class);
+            childrenTaskInfos = source.readArrayList(RecentTaskInfo.class.getClassLoader());
             lastSnapshotData.taskSize = source.readTypedObject(Point.CREATOR);
             lastSnapshotData.contentInsets = source.readTypedObject(Rect.CREATOR);
             lastSnapshotData.bufferSize = source.readTypedObject(Point.CREATOR);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d90010e..7ac4bdd 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4535,6 +4535,12 @@
         // we are back active so skip it.
         unscheduleGcIdler();
 
+        // To investigate "duplciate Application objects" bug (b/185177290)
+        if (UserHandle.myUserId() != UserHandle.getUserId(data.info.applicationInfo.uid)) {
+            Slog.wtf(TAG, "handleCreateService called with wrong appinfo UID: myUserId="
+                    + UserHandle.myUserId() + " appinfo.uid=" + data.info.applicationInfo.uid);
+        }
+
         LoadedApk packageInfo = getPackageInfoNoCheck(
                 data.info.applicationInfo, data.compatInfo);
         Service service = null;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 68c69e5..565f690 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -4058,7 +4058,7 @@
                 LongSparseArray<NoteOpEvent> array = new LongSparseArray<>(numEntries);
 
                 for (int i = 0; i < numEntries; i++) {
-                    array.put(source.readLong(), source.readParcelable(null, android.app.AppOpsManager.NoteOpEvent.class));
+                    array.put(source.readLong(), source.readParcelable(null));
                 }
 
                 return array;
@@ -5178,7 +5178,7 @@
             final int[] uids = parcel.createIntArray();
             if (!ArrayUtils.isEmpty(uids)) {
                 final ParceledListSlice<HistoricalUidOps> listSlice = parcel.readParcelable(
-                        HistoricalOps.class.getClassLoader(), android.content.pm.ParceledListSlice.class);
+                        HistoricalOps.class.getClassLoader());
                 final List<HistoricalUidOps> uidOps = (listSlice != null)
                         ? listSlice.getList() : null;
                 if (uidOps == null) {
@@ -10000,7 +10000,7 @@
 
     private static @Nullable List<AttributedOpEntry> readDiscreteAccessArrayFromParcel(
             @NonNull Parcel parcel) {
-        final ParceledListSlice<AttributedOpEntry> listSlice = parcel.readParcelable(null, android.content.pm.ParceledListSlice.class);
+        final ParceledListSlice<AttributedOpEntry> listSlice = parcel.readParcelable(null);
         return listSlice == null ? null : listSlice.getList();
     }
 
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index c0aebee..7a806bd 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -118,11 +118,11 @@
             name = source.readString();
         }
         interruptionFilter = source.readInt();
-        conditionId = source.readParcelable(null, android.net.Uri.class);
-        owner = source.readParcelable(null, android.content.ComponentName.class);
-        configurationActivity = source.readParcelable(null, android.content.ComponentName.class);
+        conditionId = source.readParcelable(null);
+        owner = source.readParcelable(null);
+        configurationActivity = source.readParcelable(null);
         creationTime = source.readLong();
-        mZenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class);
+        mZenPolicy = source.readParcelable(null);
         mModified = source.readInt() == ENABLED;
         mPkg = source.readString();
     }
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 0bb6ffa..7812aba 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -430,8 +430,8 @@
      * permissions set by {@link #setRequireAllOfPermissions(String[])}, and none of the
      * permissions set by {@link #setRequireNoneOfPermissions(String[])} to get the broadcast.
      *
-     * @param requiredPermissions a list of Strings of permission the receiver must have, or null
-     *                            to clear any previously set value.
+     * @param requiredPermissions a list of Strings of permission the receiver must have. Set to
+     *                            null or an empty array to clear any previously set value.
      * @hide
      */
     @SystemApi
@@ -449,8 +449,8 @@
      * permissions set by {@link #setRequireAllOfPermissions(String[])}, and none of the
      * permissions set by {@link #setRequireNoneOfPermissions(String[])} to get the broadcast.
      *
-     * @param excludedPermissions a list of Strings of permission the receiver must not have,
-     *                            or null to clear any previously set value.
+     * @param excludedPermissions a list of Strings of permission the receiver must not have. Set to
+     *                            null or an empty array to clear any previously set value.
      * @hide
      */
     @SystemApi
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index f3e9f10..fe512ff 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -113,6 +113,7 @@
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -2170,6 +2171,11 @@
     }
 
     @Override
+    public void selfRevokePermissions(@NonNull Collection<String> permissions) {
+        getSystemService(PermissionManager.class).selfRevokePermissions(permissions);
+    }
+
+    @Override
     public int checkCallingPermission(String permission) {
         if (permission == null) {
             throw new IllegalArgumentException("permission is null");
diff --git a/core/java/android/app/GrantedUriPermission.java b/core/java/android/app/GrantedUriPermission.java
index a71cb4a..48d5b8c 100644
--- a/core/java/android/app/GrantedUriPermission.java
+++ b/core/java/android/app/GrantedUriPermission.java
@@ -68,7 +68,7 @@
             };
 
     private GrantedUriPermission(Parcel in) {
-        uri = in.readParcelable(null, android.net.Uri.class);
+        uri = in.readParcelable(null);
         packageName = in.readString();
     }
 }
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index b052bc5..a9ec11e 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -178,17 +178,16 @@
     Point getAppTaskThumbnailSize();
     /**
      * Only callable from the system. This token grants a temporary permission to call
-     * #startActivityAsCallerWithToken. The token will time out after
-     * START_AS_CALLER_TOKEN_TIMEOUT if it is not used.
+     * #startActivityAsCaller. The token will time out after START_AS_CALLER_TOKEN_TIMEOUT
+     * if it is not used.
      *
-     * @param delegatorToken The Binder token referencing the system Activity that wants to delegate
-     *        the #startActivityAsCaller to another app. The "caller" will be the caller of this
-     *        activity's token, not the delegate's caller (which is probably the delegator itself).
+     * @param componentName The component name of the delegated component that is allowed to
+     *                      call #startActivityAsCaller with the returned token.
      *
      * @return Returns a token that can be given to a "delegate" app that may call
      *         #startActivityAsCaller
      */
-    IBinder requestStartActivityPermissionToken(in IBinder delegatorToken);
+    IBinder requestStartActivityPermissionToken(in ComponentName componentName);
 
     oneway void releaseSomeActivities(in IApplicationThread app);
     Bitmap getTaskDescriptionIcon(in String filename, int userId);
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index dc71a32..14afd0f 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -17,6 +17,7 @@
 package android.app;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -40,8 +41,10 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.persistentdata.IPersistentDataBlockService;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.view.IOnKeyguardExitResult;
 import android.view.IWindowManager;
@@ -49,6 +52,9 @@
 import android.view.WindowManagerGlobal;
 
 import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.internal.util.Preconditions;
+import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
+import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternView;
 import com.android.internal.widget.LockscreenCredential;
@@ -57,6 +63,8 @@
 import java.nio.charset.Charset;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
 
 /**
  * Class that can be used to lock and unlock the keyguard. The
@@ -69,10 +77,13 @@
     private static final String TAG = "KeyguardManager";
 
     private final Context mContext;
+    private final LockPatternUtils mLockPatternUtils;
     private final IWindowManager mWM;
     private final IActivityManager mAm;
     private final ITrustManager mTrustManager;
     private final INotificationManager mNotificationManager;
+    private final ArrayMap<WeakEscrowTokenRemovedListener, IWeakEscrowTokenRemovedListener>
+            mListeners = new ArrayMap<>();
 
     /**
      * Intent used to prompt user for device credentials.
@@ -455,8 +466,42 @@
         public void onDismissCancelled() { }
     }
 
+    /**
+     * Callback passed to
+     * {@link KeyguardManager#addWeakEscrowToken}
+     * to notify caller of state change.
+     * @hide
+     */
+    @SystemApi
+    public interface WeakEscrowTokenActivatedListener {
+        /**
+         * The method to be called when the token is activated.
+         * @param handle 64 bit handle corresponding to the escrow token
+         * @param user user for whom the weak escrow token has been added
+         */
+        void onWeakEscrowTokenActivated(long handle, @NonNull UserHandle user);
+    }
+
+    /**
+     * Listener passed to
+     * {@link KeyguardManager#registerWeakEscrowTokenRemovedListener} and
+     * {@link KeyguardManager#unregisterWeakEscrowTokenRemovedListener}
+     * to notify caller of an weak escrow token has been removed.
+     * @hide
+     */
+    @SystemApi
+    public interface WeakEscrowTokenRemovedListener {
+        /**
+         * The method to be called when the token is removed.
+         * @param handle 64 bit handle corresponding to the escrow token
+         * @param user user for whom the escrow token has been added
+         */
+        void onWeakEscrowTokenRemoved(long handle, @NonNull UserHandle user);
+    }
+
     KeyguardManager(Context context) throws ServiceNotFoundException {
         mContext = context;
+        mLockPatternUtils = new LockPatternUtils(context);
         mWM = WindowManagerGlobal.getWindowManagerService();
         mAm = ActivityManager.getService();
         mTrustManager = ITrustManager.Stub.asInterface(
@@ -785,7 +830,6 @@
             return false;
         }
 
-        LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
         int userId = mContext.getUserId();
         if (isDeviceSecure(userId)) {
             Log.e(TAG, "Password already set, rejecting call to setLock");
@@ -799,7 +843,7 @@
         try {
             LockscreenCredential credential = createLockscreenCredential(
                     lockType, password);
-            success = lockPatternUtils.setLockCredential(
+            success = mLockPatternUtils.setLockCredential(
                     credential,
                     /* savedPassword= */ LockscreenCredential.createNone(),
                     userId);
@@ -813,6 +857,150 @@
     }
 
     /**
+     * Create a weak escrow token for the current user, which can later be used to unlock FBE
+     * or change user password.
+     *
+     * After adding, if the user currently  has a secure lockscreen, they will need to perform a
+     * confirm credential operation in order to activate the token for future use. If the user
+     * has no secure lockscreen, then the token is activated immediately.
+     *
+     * If the user changes or removes the lockscreen password, any activated weak escrow token will
+     * be removed.
+     *
+     * @return a unique 64-bit token handle which is needed to refer to this token later.
+     * @hide
+     */
+    @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
+    @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
+    @SystemApi
+    public long addWeakEscrowToken(@NonNull byte[] token, @NonNull UserHandle user,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull WeakEscrowTokenActivatedListener listener) {
+        Objects.requireNonNull(token, "Token cannot be null.");
+        Objects.requireNonNull(user, "User cannot be null.");
+        Objects.requireNonNull(executor, "Executor cannot be null.");
+        Objects.requireNonNull(listener, "Listener cannot be null.");
+        int userId = user.getIdentifier();
+        IWeakEscrowTokenActivatedListener internalListener =
+                new IWeakEscrowTokenActivatedListener.Stub() {
+            @Override
+            public void onWeakEscrowTokenActivated(long handle, int userId) {
+                UserHandle user = UserHandle.of(userId);
+                final long restoreToken = Binder.clearCallingIdentity();
+                try {
+                    executor.execute(() -> listener.onWeakEscrowTokenActivated(handle, user));
+                } finally {
+                    Binder.restoreCallingIdentity(restoreToken);
+                }
+                Log.i(TAG, "Weak escrow token activated.");
+            }
+        };
+        return mLockPatternUtils.addWeakEscrowToken(token, userId, internalListener);
+    }
+
+    /**
+     * Remove a weak escrow token.
+     *
+     * @return true if the given handle refers to a valid weak token previously returned from
+     * {@link #addWeakEscrowToken}, whether it's active or not. return false otherwise.
+     * @hide
+     */
+    @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
+    @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
+    @SystemApi
+    public boolean removeWeakEscrowToken(long handle, @NonNull UserHandle user) {
+        Objects.requireNonNull(user, "User cannot be null.");
+        return mLockPatternUtils.removeWeakEscrowToken(handle, user.getIdentifier());
+    }
+
+    /**
+     * Check if the given weak escrow token is active or not.
+     * @hide
+     */
+    @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
+    @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
+    @SystemApi
+    public boolean isWeakEscrowTokenActive(long handle, @NonNull UserHandle user) {
+        Objects.requireNonNull(user, "User cannot be null.");
+        return mLockPatternUtils.isWeakEscrowTokenActive(handle, user.getIdentifier());
+    }
+
+    /**
+     * Check if the given weak escrow token is validate.
+     * @hide
+     */
+    @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
+    @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
+    @SystemApi
+    public boolean isWeakEscrowTokenValid(long handle, @NonNull byte[] token,
+            @NonNull UserHandle user) {
+        Objects.requireNonNull(token, "Token cannot be null.");
+        Objects.requireNonNull(user, "User cannot be null.");
+        return mLockPatternUtils.isWeakEscrowTokenValid(handle, token, user.getIdentifier());
+    }
+
+    /**
+     * Register the given WeakEscrowTokenRemovedListener.
+     *
+     * @return true if the listener is registered successfully, return false otherwise.
+     * @hide
+     */
+    @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
+    @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
+    @SystemApi
+    public boolean registerWeakEscrowTokenRemovedListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull WeakEscrowTokenRemovedListener listener) {
+        Objects.requireNonNull(listener, "Listener cannot be null.");
+        Objects.requireNonNull(executor, "Executor cannot be null.");
+        Preconditions.checkArgument(!mListeners.containsKey(listener),
+                "Listener already registered: %s", listener);
+        IWeakEscrowTokenRemovedListener internalListener =
+                new IWeakEscrowTokenRemovedListener.Stub() {
+            @Override
+            public void onWeakEscrowTokenRemoved(long handle, int userId) {
+                UserHandle user = UserHandle.of(userId);
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    executor.execute(() -> listener.onWeakEscrowTokenRemoved(handle, user));
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        };
+        if (mLockPatternUtils.registerWeakEscrowTokenRemovedListener(internalListener)) {
+            mListeners.put(listener, internalListener);
+            return true;
+        } else {
+            Log.e(TAG, "Listener failed to register");
+            return false;
+        }
+    }
+
+    /**
+     * Unregister the given WeakEscrowTokenRemovedListener.
+     *
+     * @return true if the listener is unregistered successfully, return false otherwise.
+     * @hide
+     */
+    @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
+    @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
+    @SystemApi
+    public boolean unregisterWeakEscrowTokenRemovedListener(
+            @NonNull WeakEscrowTokenRemovedListener listener) {
+        Objects.requireNonNull(listener, "Listener cannot be null.");
+        IWeakEscrowTokenRemovedListener internalListener = mListeners.get(listener);
+        Preconditions.checkArgument(internalListener != null, "Listener was not registered");
+        if (mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(internalListener)) {
+            mListeners.remove(listener);
+            return true;
+        } else {
+            Log.e(TAG, "Listener failed to unregister.");
+            return false;
+        }
+    }
+
+    /**
      * Set the lockscreen password to {@code newPassword} after validating the current password
      * against {@code currentPassword}.
      * <p>If no password is currently set, {@code currentPassword} should be set to {@code null}.
@@ -832,13 +1020,12 @@
     })
     public boolean setLock(@LockTypes int newLockType, @Nullable byte[] newPassword,
             @LockTypes int currentLockType, @Nullable byte[] currentPassword) {
-        final LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
         final int userId = mContext.getUserId();
         LockscreenCredential currentCredential = createLockscreenCredential(
                 currentLockType, currentPassword);
         LockscreenCredential newCredential = createLockscreenCredential(
                 newLockType, newPassword);
-        return lockPatternUtils.setLockCredential(newCredential, currentCredential, userId);
+        return mLockPatternUtils.setLockCredential(newCredential, currentCredential, userId);
     }
 
     /**
@@ -857,10 +1044,9 @@
             Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE
     })
     public boolean checkLock(@LockTypes int lockType, @Nullable byte[] password) {
-        final LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
         final LockscreenCredential credential = createLockscreenCredential(
                 lockType, password);
-        final VerifyCredentialResponse response = lockPatternUtils.verifyCredential(
+        final VerifyCredentialResponse response = mLockPatternUtils.verifyCredential(
                 credential, mContext.getUserId(), /* flags= */ 0);
         if (response == null) {
             return false;
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 77af474..4e32e9a 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1341,15 +1341,43 @@
         return mResources;
     }
 
+    /**
+     * Used to investigate "duplicate app objects" bug (b/185177290).
+     * makeApplication() should only be called on the main thread, so no synchronization should
+     * be needed, but syncing anyway just in case.
+     */
+    @GuardedBy("sApplicationCache")
+    private static final ArrayMap<String, Application> sApplicationCache = new ArrayMap<>(4);
+
     @UnsupportedAppUsage
     public Application makeApplication(boolean forceDefaultAppClass,
             Instrumentation instrumentation) {
         if (mApplication != null) {
             return mApplication;
         }
-
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");
 
+        // For b/185177290.
+        final boolean wrongUser =
+                UserHandle.myUserId() != UserHandle.getUserId(mApplicationInfo.uid);
+        if (wrongUser) {
+            Slog.wtf(TAG, "makeApplication called with wrong appinfo UID: myUserId="
+                    + UserHandle.myUserId() + " appinfo.uid=" + mApplicationInfo.uid);
+        }
+        synchronized (sApplicationCache) {
+            final Application cached = sApplicationCache.get(mPackageName);
+            if (cached != null) {
+                // Looks like this is always happening for the system server, because
+                // the LoadedApk created in systemMain() -> attach() isn't cached properly?
+                if (!"android".equals(mPackageName)) {
+                    Slog.wtf(TAG, "App instance already created for package=" + mPackageName
+                            + " instance=" + cached);
+                }
+                // TODO Return the cached one, unles it's for the wrong user?
+                // For now, we just add WTF checks.
+            }
+        }
+
         Application app = null;
 
         final String myProcessName = Process.myProcessName();
@@ -1397,6 +1425,9 @@
         }
         mActivityThread.mAllApplications.add(app);
         mApplication = app;
+        synchronized (sApplicationCache) {
+            sApplicationCache.put(mPackageName, app);
+        }
 
         if (instrumentation != null) {
             try {
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
index f97415c..cd6df0b 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -100,7 +100,7 @@
         } else {
             mDescription = null;
         }
-        in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader(), android.app.NotificationChannel.class);
+        in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader());
         mBlocked = in.readBoolean();
         mUserLockedFields = in.readInt();
     }
diff --git a/core/java/android/app/RemoteInputHistoryItem.java b/core/java/android/app/RemoteInputHistoryItem.java
index 32f8981..091db3f 100644
--- a/core/java/android/app/RemoteInputHistoryItem.java
+++ b/core/java/android/app/RemoteInputHistoryItem.java
@@ -48,7 +48,7 @@
     protected RemoteInputHistoryItem(Parcel in) {
         mText = in.readCharSequence();
         mMimeType = in.readStringNoHelper();
-        mUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
+        mUri = in.readParcelable(Uri.class.getClassLoader());
     }
 
     public static final Creator<RemoteInputHistoryItem> CREATOR =
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 7969cda..a60de08 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -488,8 +488,7 @@
      *
      * @hide
      */
-    // TODO(b/208628038): Uncomment when the permission is in place
-    //  @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+    @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP)
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     @SystemApi
     public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE =
@@ -535,8 +534,7 @@
      *
      * @hide
      */
-    // TODO(b/208628038): Uncomment when the permission is in place
-    //  @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+    @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP)
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     @SystemApi
     public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE =
@@ -561,8 +559,7 @@
      *
      * @hide
      */
-    // TODO(b/208628038): Uncomment when the permission is in place
-    //  @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+    @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP)
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     @SystemApi
     public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION =
@@ -2859,8 +2856,7 @@
      *
      * @hide
      */
-    // TODO(b/208628038): Uncomment when the permission is in place
-    //  @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+    @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP)
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     @SystemApi
     public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER =
@@ -2885,6 +2881,38 @@
     public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR = 2;
 
     /**
+     * An {@link Intent} extra which resolves to a custom user consent screen.
+     *
+     * <p>If this extra is provided to the device management role holder via either {@link
+     * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} or {@link
+     * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE}, the device management role holder must
+     * launch this intent which shows the custom user consent screen, replacing its own standard
+     * consent screen.
+     *
+     * <p>If this extra is provided, it is the responsibility of the intent handler to show the
+     * list of disclaimers which are normally shown by the standard consent screen:
+     * <ul>
+     *     <li>Disclaimers set by the IT admin via the {@link #EXTRA_PROVISIONING_DISCLAIMERS}
+     *     provisioning extra</li>
+     *     <li>For fully-managed device provisioning, disclaimers defined in system apps via the
+     *     {@link #EXTRA_PROVISIONING_DISCLAIMER_HEADER} and {@link
+     *     #EXTRA_PROVISIONING_DISCLAIMER_CONTENT} metadata in their manifests</li>
+     *     <li>General disclaimer relevant to the provisioning mode</li>
+     * </ul>
+     *
+     * <p>If the custom consent screens are granted by the user {@link Activity#RESULT_OK} is
+     * returned, otherwise {@link Activity#RESULT_CANCELED} is returned. The device management
+     * role holder should ensure that the provisioning flow terminates immediately if consent
+     * is not granted by the user.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
+    public static final String EXTRA_PROVISIONING_ROLE_HOLDER_CUSTOM_USER_CONSENT_INTENT =
+            "android.app.extra.PROVISIONING_ROLE_HOLDER_CUSTOM_USER_CONSENT_INTENT";
+
+    /**
      * Maximum supported password length. Kind-of arbitrary.
      * @hide
      */
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 6e49e95..e1f6af0 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -815,13 +815,13 @@
                     mAutofillHints = in.readStringArray();
                 }
                 if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VALUE) != 0) {
-                    mAutofillValue = in.readParcelable(null, android.view.autofill.AutofillValue.class);
+                    mAutofillValue = in.readParcelable(null);
                 }
                 if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_OPTIONS) != 0) {
                     mAutofillOptions = in.readCharSequenceArray();
                 }
                 if ((autofillFlags & AUTOFILL_FLAGS_HAS_HTML_INFO) != 0) {
-                    mHtmlInfo = in.readParcelable(null, android.view.ViewStructure.HtmlInfo.class);
+                    mHtmlInfo = in.readParcelable(null);
                 }
                 if ((autofillFlags & AUTOFILL_FLAGS_HAS_MIN_TEXT_EMS) != 0) {
                     mMinEms = in.readInt();
@@ -886,7 +886,7 @@
                 mWebDomain = in.readString();
             }
             if ((flags&FLAGS_HAS_LOCALE_LIST) != 0) {
-                mLocaleList = in.readParcelable(null, android.os.LocaleList.class);
+                mLocaleList = in.readParcelable(null);
             }
             if ((flags & FLAGS_HAS_MIME_TYPES) != 0) {
                 mReceiveContentMimeTypes = in.readStringArray();
diff --git a/core/java/android/app/people/ConversationChannel.java b/core/java/android/app/people/ConversationChannel.java
index ab350f2..2bf71b0 100644
--- a/core/java/android/app/people/ConversationChannel.java
+++ b/core/java/android/app/people/ConversationChannel.java
@@ -83,16 +83,16 @@
     }
 
     public ConversationChannel(Parcel in) {
-        mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader(), android.content.pm.ShortcutInfo.class);
+        mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader());
         mUid = in.readInt();
-        mNotificationChannel = in.readParcelable(NotificationChannel.class.getClassLoader(), android.app.NotificationChannel.class);
+        mNotificationChannel = in.readParcelable(NotificationChannel.class.getClassLoader());
         mNotificationChannelGroup =
-                in.readParcelable(NotificationChannelGroup.class.getClassLoader(), android.app.NotificationChannelGroup.class);
+                in.readParcelable(NotificationChannelGroup.class.getClassLoader());
         mLastEventTimestamp = in.readLong();
         mHasActiveNotifications = in.readBoolean();
         mHasBirthdayToday = in.readBoolean();
         mStatuses = new ArrayList<>();
-        in.readParcelableList(mStatuses, ConversationStatus.class.getClassLoader(), android.app.people.ConversationStatus.class);
+        in.readParcelableList(mStatuses, ConversationStatus.class.getClassLoader());
     }
 
     @Override
diff --git a/core/java/android/app/people/ConversationStatus.java b/core/java/android/app/people/ConversationStatus.java
index a7b61b3..8038158 100644
--- a/core/java/android/app/people/ConversationStatus.java
+++ b/core/java/android/app/people/ConversationStatus.java
@@ -126,7 +126,7 @@
         mActivity = p.readInt();
         mAvailability = p.readInt();
         mDescription = p.readCharSequence();
-        mIcon = p.readParcelable(Icon.class.getClassLoader(), android.graphics.drawable.Icon.class);
+        mIcon = p.readParcelable(Icon.class.getClassLoader());
         mStartTimeMs = p.readLong();
         mEndTimeMs = p.readLong();
     }
diff --git a/core/java/android/app/people/PeopleSpaceTile.java b/core/java/android/app/people/PeopleSpaceTile.java
index 4337111..e11861f 100644
--- a/core/java/android/app/people/PeopleSpaceTile.java
+++ b/core/java/android/app/people/PeopleSpaceTile.java
@@ -472,9 +472,9 @@
     public PeopleSpaceTile(Parcel in) {
         mId = in.readString();
         mUserName = in.readCharSequence();
-        mUserIcon = in.readParcelable(Icon.class.getClassLoader(), android.graphics.drawable.Icon.class);
-        mContactUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
-        mUserHandle = in.readParcelable(UserHandle.class.getClassLoader(), android.os.UserHandle.class);
+        mUserIcon = in.readParcelable(Icon.class.getClassLoader());
+        mContactUri = in.readParcelable(Uri.class.getClassLoader());
+        mUserHandle = in.readParcelable(UserHandle.class.getClassLoader());
         mPackageName = in.readString();
         mBirthdayText = in.readString();
         mLastInteractionTimestamp = in.readLong();
@@ -483,12 +483,12 @@
         mNotificationContent = in.readCharSequence();
         mNotificationSender = in.readCharSequence();
         mNotificationCategory = in.readString();
-        mNotificationDataUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
+        mNotificationDataUri = in.readParcelable(Uri.class.getClassLoader());
         mMessagesCount = in.readInt();
-        mIntent = in.readParcelable(Intent.class.getClassLoader(), android.content.Intent.class);
+        mIntent = in.readParcelable(Intent.class.getClassLoader());
         mNotificationTimestamp = in.readLong();
         mStatuses = new ArrayList<>();
-        in.readParcelableList(mStatuses, ConversationStatus.class.getClassLoader(), android.app.people.ConversationStatus.class);
+        in.readParcelableList(mStatuses, ConversationStatus.class.getClassLoader());
         mCanBypassDnd = in.readBoolean();
         mIsPackageSuspended = in.readBoolean();
         mIsUserQuieted = in.readBoolean();
diff --git a/core/java/android/app/prediction/AppTargetEvent.java b/core/java/android/app/prediction/AppTargetEvent.java
index 51e3953..963e750 100644
--- a/core/java/android/app/prediction/AppTargetEvent.java
+++ b/core/java/android/app/prediction/AppTargetEvent.java
@@ -72,7 +72,7 @@
     }
 
     private AppTargetEvent(Parcel parcel) {
-        mTarget = parcel.readParcelable(null, android.app.prediction.AppTarget.class);
+        mTarget = parcel.readParcelable(null);
         mLocation = parcel.readString();
         mAction = parcel.readInt();
     }
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 30a6c31..fbb37db 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -197,11 +197,11 @@
         if (readActivityToken) {
             mActivityToken = in.readStrongBinder();
         }
-        mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader(), android.app.servertransaction.ActivityLifecycleItem.class);
+        mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader());
         final boolean readActivityCallbacks = in.readBoolean();
         if (readActivityCallbacks) {
             mActivityCallbacks = new ArrayList<>();
-            in.readParcelableList(mActivityCallbacks, getClass().getClassLoader(), android.app.servertransaction.ClientTransactionItem.class);
+            in.readParcelableList(mActivityCallbacks, getClass().getClassLoader());
         }
     }
 
diff --git a/core/java/android/app/smartspace/SmartspaceTargetEvent.java b/core/java/android/app/smartspace/SmartspaceTargetEvent.java
index 89caab7..61f8723 100644
--- a/core/java/android/app/smartspace/SmartspaceTargetEvent.java
+++ b/core/java/android/app/smartspace/SmartspaceTargetEvent.java
@@ -96,7 +96,7 @@
     }
 
     private SmartspaceTargetEvent(Parcel parcel) {
-        mSmartspaceTarget = parcel.readParcelable(null, android.app.smartspace.SmartspaceTarget.class);
+        mSmartspaceTarget = parcel.readParcelable(null);
         mSmartspaceActionId = parcel.readString();
         mEventType = parcel.readInt();
     }
diff --git a/core/java/android/app/time/ExternalTimeSuggestion.java b/core/java/android/app/time/ExternalTimeSuggestion.java
index 0f98b44..8e281c0 100644
--- a/core/java/android/app/time/ExternalTimeSuggestion.java
+++ b/core/java/android/app/time/ExternalTimeSuggestion.java
@@ -101,11 +101,11 @@
     }
 
     private static ExternalTimeSuggestion createFromParcel(Parcel in) {
-        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */, android.os.TimestampedValue.class);
+        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
         ExternalTimeSuggestion suggestion =
                 new ExternalTimeSuggestion(utcTime.getReferenceTimeMillis(), utcTime.getValue());
         @SuppressWarnings("unchecked")
-        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class);
+        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
         suggestion.mDebugInfo = debugInfo;
         return suggestion;
     }
diff --git a/core/java/android/app/time/TimeCapabilitiesAndConfig.java b/core/java/android/app/time/TimeCapabilitiesAndConfig.java
index 71fce14..4a10447 100644
--- a/core/java/android/app/time/TimeCapabilitiesAndConfig.java
+++ b/core/java/android/app/time/TimeCapabilitiesAndConfig.java
@@ -59,8 +59,8 @@
 
     @NonNull
     private static TimeCapabilitiesAndConfig readFromParcel(Parcel in) {
-        TimeCapabilities capabilities = in.readParcelable(null, android.app.time.TimeCapabilities.class);
-        TimeConfiguration configuration = in.readParcelable(null, android.app.time.TimeConfiguration.class);
+        TimeCapabilities capabilities = in.readParcelable(null);
+        TimeConfiguration configuration = in.readParcelable(null);
         return new TimeCapabilitiesAndConfig(capabilities, configuration);
     }
 
diff --git a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
index cd91b04..a9ea76f 100644
--- a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
+++ b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
@@ -61,8 +61,8 @@
 
     @NonNull
     private static TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) {
-        TimeZoneCapabilities capabilities = in.readParcelable(null, android.app.time.TimeZoneCapabilities.class);
-        TimeZoneConfiguration configuration = in.readParcelable(null, android.app.time.TimeZoneConfiguration.class);
+        TimeZoneCapabilities capabilities = in.readParcelable(null);
+        TimeZoneConfiguration configuration = in.readParcelable(null);
         return new TimeZoneCapabilitiesAndConfig(capabilities, configuration);
     }
 
diff --git a/core/java/android/app/timedetector/GnssTimeSuggestion.java b/core/java/android/app/timedetector/GnssTimeSuggestion.java
index 8ccff62..6478a2d 100644
--- a/core/java/android/app/timedetector/GnssTimeSuggestion.java
+++ b/core/java/android/app/timedetector/GnssTimeSuggestion.java
@@ -66,10 +66,10 @@
     }
 
     private static GnssTimeSuggestion createFromParcel(Parcel in) {
-        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */, android.os.TimestampedValue.class);
+        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
         GnssTimeSuggestion suggestion = new GnssTimeSuggestion(utcTime);
         @SuppressWarnings("unchecked")
-        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class);
+        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
         suggestion.mDebugInfo = debugInfo;
         return suggestion;
     }
diff --git a/core/java/android/app/timedetector/ManualTimeSuggestion.java b/core/java/android/app/timedetector/ManualTimeSuggestion.java
index 1699a5f..299e951 100644
--- a/core/java/android/app/timedetector/ManualTimeSuggestion.java
+++ b/core/java/android/app/timedetector/ManualTimeSuggestion.java
@@ -66,10 +66,10 @@
     }
 
     private static ManualTimeSuggestion createFromParcel(Parcel in) {
-        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */, android.os.TimestampedValue.class);
+        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
         ManualTimeSuggestion suggestion = new ManualTimeSuggestion(utcTime);
         @SuppressWarnings("unchecked")
-        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class);
+        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
         suggestion.mDebugInfo = debugInfo;
         return suggestion;
     }
diff --git a/core/java/android/app/timedetector/NetworkTimeSuggestion.java b/core/java/android/app/timedetector/NetworkTimeSuggestion.java
index 2030083..a5259c2 100644
--- a/core/java/android/app/timedetector/NetworkTimeSuggestion.java
+++ b/core/java/android/app/timedetector/NetworkTimeSuggestion.java
@@ -66,10 +66,10 @@
     }
 
     private static NetworkTimeSuggestion createFromParcel(Parcel in) {
-        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */, android.os.TimestampedValue.class);
+        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
         NetworkTimeSuggestion suggestion = new NetworkTimeSuggestion(utcTime);
         @SuppressWarnings("unchecked")
-        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class);
+        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
         suggestion.mDebugInfo = debugInfo;
         return suggestion;
     }
diff --git a/core/java/android/app/timedetector/TelephonyTimeSuggestion.java b/core/java/android/app/timedetector/TelephonyTimeSuggestion.java
index 52d0bbe..6c3a304 100644
--- a/core/java/android/app/timedetector/TelephonyTimeSuggestion.java
+++ b/core/java/android/app/timedetector/TelephonyTimeSuggestion.java
@@ -77,10 +77,10 @@
     private static TelephonyTimeSuggestion createFromParcel(Parcel in) {
         int slotIndex = in.readInt();
         TelephonyTimeSuggestion suggestion = new TelephonyTimeSuggestion.Builder(slotIndex)
-                .setUtcTime(in.readParcelable(null /* classLoader */, android.os.TimestampedValue.class))
+                .setUtcTime(in.readParcelable(null /* classLoader */))
                 .build();
         @SuppressWarnings("unchecked")
-        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class);
+        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
         if (debugInfo != null) {
             suggestion.addDebugInfo(debugInfo);
         }
diff --git a/core/java/android/app/timezone/RulesState.java b/core/java/android/app/timezone/RulesState.java
index 516ad03..ee88ec54 100644
--- a/core/java/android/app/timezone/RulesState.java
+++ b/core/java/android/app/timezone/RulesState.java
@@ -195,12 +195,12 @@
 
     private static RulesState createFromParcel(Parcel in) {
         String baseRulesVersion = in.readString();
-        DistroFormatVersion distroFormatVersionSupported = in.readParcelable(null, android.app.timezone.DistroFormatVersion.class);
+        DistroFormatVersion distroFormatVersionSupported = in.readParcelable(null);
         boolean operationInProgress = in.readByte() == BYTE_TRUE;
         int distroStagedState = in.readByte();
-        DistroRulesVersion stagedDistroRulesVersion = in.readParcelable(null, android.app.timezone.DistroRulesVersion.class);
+        DistroRulesVersion stagedDistroRulesVersion = in.readParcelable(null);
         int installedDistroStatus = in.readByte();
-        DistroRulesVersion installedDistroRulesVersion = in.readParcelable(null, android.app.timezone.DistroRulesVersion.class);
+        DistroRulesVersion installedDistroRulesVersion = in.readParcelable(null);
         return new RulesState(baseRulesVersion, distroFormatVersionSupported, operationInProgress,
                 distroStagedState, stagedDistroRulesVersion,
                 installedDistroStatus, installedDistroRulesVersion);
diff --git a/core/java/android/app/timezonedetector/ManualTimeZoneSuggestion.java b/core/java/android/app/timezonedetector/ManualTimeZoneSuggestion.java
index 387319e..01a60b1 100644
--- a/core/java/android/app/timezonedetector/ManualTimeZoneSuggestion.java
+++ b/core/java/android/app/timezonedetector/ManualTimeZoneSuggestion.java
@@ -65,7 +65,7 @@
         String zoneId = in.readString();
         ManualTimeZoneSuggestion suggestion = new ManualTimeZoneSuggestion(zoneId);
         @SuppressWarnings("unchecked")
-        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class);
+        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
         suggestion.mDebugInfo = debugInfo;
         return suggestion;
     }
diff --git a/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java b/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java
index e5b4e46..eb6750f 100644
--- a/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java
+++ b/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java
@@ -165,7 +165,7 @@
                 .setQuality(in.readInt())
                 .build();
         List<String> debugInfo =
-                in.readArrayList(TelephonyTimeZoneSuggestion.class.getClassLoader(), java.lang.String.class);
+                in.readArrayList(TelephonyTimeZoneSuggestion.class.getClassLoader());
         if (debugInfo != null) {
             suggestion.addDebugInfo(debugInfo);
         }
diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java
index 65b2775..177de83 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -156,7 +156,7 @@
 
                 @Override
                 public void onTrustError(CharSequence message) {
-                    Message m = mHandler.obtainMessage(MSG_TRUST_ERROR);
+                    Message m = mHandler.obtainMessage(MSG_TRUST_ERROR, trustListener);
                     m.getData().putCharSequence(DATA_MESSAGE, message);
                     m.sendToTarget();
                 }
diff --git a/core/java/android/app/usage/CacheQuotaHint.java b/core/java/android/app/usage/CacheQuotaHint.java
index ba6bcdc..0ccb058 100644
--- a/core/java/android/app/usage/CacheQuotaHint.java
+++ b/core/java/android/app/usage/CacheQuotaHint.java
@@ -148,7 +148,7 @@
                     return builder.setVolumeUuid(in.readString())
                             .setUid(in.readInt())
                             .setQuota(in.readLong())
-                            .setUsageStats(in.readParcelable(UsageStats.class.getClassLoader(), android.app.usage.UsageStats.class))
+                            .setUsageStats(in.readParcelable(UsageStats.class.getClassLoader()))
                             .build();
                 }
 
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 856a8e1..661291c 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -28,8 +28,7 @@
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SuppressLint;
-import android.annotation.SystemApi;
-import android.app.PropertyInvalidatedCache;
+import android.annotation.SystemApi; //import android.app.PropertyInvalidatedCache;
 import android.bluetooth.BluetoothDevice.Transport;
 import android.bluetooth.BluetoothProfile.ConnectionPolicy;
 import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission;
@@ -687,14 +686,15 @@
             "android.bluetooth.adapter.action.BLE_ACL_DISCONNECTED";
 
     /** The profile is in disconnected state */
-    public static final int STATE_DISCONNECTED = BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTED;
+    public static final int STATE_DISCONNECTED =
+            0; //BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTED;
     /** The profile is in connecting state */
-    public static final int STATE_CONNECTING = BluetoothProtoEnums.CONNECTION_STATE_CONNECTING;
+    public static final int STATE_CONNECTING = 1; //BluetoothProtoEnums.CONNECTION_STATE_CONNECTING;
     /** The profile is in connected state */
-    public static final int STATE_CONNECTED = BluetoothProtoEnums.CONNECTION_STATE_CONNECTED;
+    public static final int STATE_CONNECTED = 2; //BluetoothProtoEnums.CONNECTION_STATE_CONNECTED;
     /** The profile is in disconnecting state */
     public static final int STATE_DISCONNECTING =
-            BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTING;
+            3; //BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTING;
 
     /** @hide */
     public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager";
@@ -1055,6 +1055,7 @@
         return false;
     }
 
+    /*
     private static final String BLUETOOTH_GET_STATE_CACHE_PROPERTY = "cache_key.bluetooth.get_state";
 
     private final PropertyInvalidatedCache<Void, Integer> mBluetoothGetStateCache =
@@ -1070,17 +1071,22 @@
                     }
                 }
             };
+     */
 
     /** @hide */
+    /*
     @RequiresNoPermission
     public void disableBluetoothGetStateCache() {
         mBluetoothGetStateCache.disableLocal();
     }
+     */
 
     /** @hide */
+    /*
     public static void invalidateBluetoothGetStateCache() {
         PropertyInvalidatedCache.invalidateCache(BLUETOOTH_GET_STATE_CACHE_PROPERTY);
     }
+     */
 
     /**
      * Fetch the current bluetooth state.  If the service is down, return
@@ -1092,14 +1098,12 @@
         try {
             mServiceLock.readLock().lock();
             if (mService != null) {
-                state = mBluetoothGetStateCache.query(null);
+                //state = mBluetoothGetStateCache.query(null);
+                state = mService.getState();
             }
-        } catch (RuntimeException e) {
-            if (e.getCause() instanceof RemoteException) {
-                Log.e(TAG, "", e.getCause());
-            } else {
-                throw e;
-            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            e.rethrowFromSystemServer();
         } finally {
             mServiceLock.readLock().unlock();
         }
@@ -2078,6 +2082,7 @@
         }
     }
 
+    /*
     private static final String BLUETOOTH_FILTERING_CACHE_PROPERTY =
             "cache_key.bluetooth.is_offloaded_filtering_supported";
     private final PropertyInvalidatedCache<Void, Boolean> mBluetoothFilteringCache =
@@ -2100,17 +2105,22 @@
 
                 }
             };
+     */
 
     /** @hide */
+    /*
     @RequiresNoPermission
     public void disableIsOffloadedFilteringSupportedCache() {
         mBluetoothFilteringCache.disableLocal();
     }
+     */
 
     /** @hide */
+    /*
     public static void invalidateIsOffloadedFilteringSupportedCache() {
         PropertyInvalidatedCache.invalidateCache(BLUETOOTH_FILTERING_CACHE_PROPERTY);
     }
+     */
 
     /**
      * Return true if offloaded filters are supported
@@ -2123,7 +2133,18 @@
         if (!getLeAccess()) {
             return false;
         }
-        return mBluetoothFilteringCache.query(null);
+        //return mBluetoothFilteringCache.query(null);
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.isOffloadedFilteringSupported();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to get isOffloadedFilteringSupported, error: ", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return false;
     }
 
     /**
@@ -2249,17 +2270,17 @@
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(value = {
-            BluetoothStatusCodes.SUCCESS,
+            BluetoothStatusCodes.FEATURE_SUPPORTED,
             BluetoothStatusCodes.ERROR_UNKNOWN,
             BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED,
-            BluetoothStatusCodes.ERROR_FEATURE_NOT_SUPPORTED,
+            BluetoothStatusCodes.FEATURE_NOT_SUPPORTED,
     })
     public @interface LeFeatureReturnValues {}
 
     /**
-     * Returns {@link BluetoothStatusCodes#SUCCESS} if the LE audio feature is
-     * supported, returns {@link BluetoothStatusCodes#ERROR_FEATURE_NOT_SUPPORTED} if
-     * the feature is not supported or an error code.
+     * Returns {@link BluetoothStatusCodes#FEATURE_SUPPORTED} if the LE audio feature is
+     * supported, {@link BluetoothStatusCodes#FEATURE_NOT_SUPPORTED} if the feature is not
+     * supported, or an error code.
      *
      * @return whether the LE audio is supported
      */
@@ -2282,9 +2303,10 @@
     }
 
     /**
-     * Returns {@link BluetoothStatusCodes#SUCCESS} if LE Periodic Advertising Sync Transfer Sender
-     * feature is supported, returns {@link BluetoothStatusCodes#ERROR_FEATURE_NOT_SUPPORTED} if the
-     * feature is not supported or an error code
+     * Returns {@link BluetoothStatusCodes#FEATURE_SUPPORTED} if LE Periodic Advertising Sync
+     * Transfer Sender feature is supported,
+     * {@link BluetoothStatusCodes#FEATURE_NOT_SUPPORTED} if the feature is not supported, or
+     * an error code
      *
      * @return whether the chipset supports the LE Periodic Advertising Sync Transfer Sender feature
      */
@@ -2529,15 +2551,13 @@
         return supportedProfiles;
     }
 
+    /*
     private static final String BLUETOOTH_GET_ADAPTER_CONNECTION_STATE_CACHE_PROPERTY =
             "cache_key.bluetooth.get_adapter_connection_state";
     private final PropertyInvalidatedCache<Void, Integer>
             mBluetoothGetAdapterConnectionStateCache =
             new PropertyInvalidatedCache<Void, Integer> (
                 8, BLUETOOTH_GET_ADAPTER_CONNECTION_STATE_CACHE_PROPERTY) {
-                /**
-                 * This method must not be called when mService is null.
-                 */
                 @Override
                 @SuppressLint("AndroidFrameworkRequiresPermission")
                 public Integer recompute(Void query) {
@@ -2548,18 +2568,23 @@
                     }
                 }
             };
+     */
 
     /** @hide */
+    /*
     @RequiresNoPermission
     public void disableGetAdapterConnectionStateCache() {
         mBluetoothGetAdapterConnectionStateCache.disableLocal();
     }
+     */
 
     /** @hide */
+    /*
     public static void invalidateGetAdapterConnectionStateCache() {
         PropertyInvalidatedCache.invalidateCache(
             BLUETOOTH_GET_ADAPTER_CONNECTION_STATE_CACHE_PROPERTY);
     }
+     */
 
     /**
      * Get the current connection state of the local Bluetooth adapter.
@@ -2583,20 +2608,18 @@
         try {
             mServiceLock.readLock().lock();
             if (mService != null) {
-                return mBluetoothGetAdapterConnectionStateCache.query(null);
+                return mService.getAdapterConnectionState();
             }
-        } catch (RuntimeException e) {
-            if (e.getCause() instanceof RemoteException) {
-                Log.e(TAG, "getConnectionState:", e.getCause());
-            } else {
-                throw e;
-            }
+            //return mBluetoothGetAdapterConnectionStateCache.query(null);
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to getConnectionState, error: ", e);
         } finally {
             mServiceLock.readLock().unlock();
         }
         return BluetoothAdapter.STATE_DISCONNECTED;
     }
 
+    /*
     private static final String BLUETOOTH_PROFILE_CACHE_PROPERTY =
             "cache_key.bluetooth.get_profile_connection_state";
     private final PropertyInvalidatedCache<Integer, Integer>
@@ -2624,17 +2647,22 @@
                                          query);
                 }
             };
+     */
 
     /** @hide */
+    /*
     @RequiresNoPermission
     public void disableGetProfileConnectionStateCache() {
         mGetProfileConnectionStateCache.disableLocal();
     }
+     */
 
     /** @hide */
+    /*
     public static void invalidateGetProfileConnectionStateCache() {
         PropertyInvalidatedCache.invalidateCache(BLUETOOTH_PROFILE_CACHE_PROPERTY);
     }
+     */
 
     /**
      * Get the current connection state of a profile.
@@ -2656,7 +2684,18 @@
         if (getState() != STATE_ON) {
             return BluetoothProfile.STATE_DISCONNECTED;
         }
-        return mGetProfileConnectionStateCache.query(new Integer(profile));
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                mService.getProfileConnectionState(profile);
+            }
+            //return mGetProfileConnectionStateCache.query(new Integer(profile));
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to getProfileConnectionState, error: ", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return BluetoothProfile.STATE_DISCONNECTED;
     }
 
     /**
@@ -3064,6 +3103,9 @@
             BluetoothCsipSetCoordinator csipSetCoordinator =
                     new BluetoothCsipSetCoordinator(context, listener, this);
             return true;
+        } else if (profile == BluetoothProfile.LE_CALL_CONTROL) {
+            BluetoothLeCallControl tbs = new BluetoothLeCallControl(context, listener);
+            return true;
         } else {
             return false;
         }
@@ -3166,6 +3208,10 @@
                         (BluetoothCsipSetCoordinator) proxy;
                 csipSetCoordinator.close();
                 break;
+            case BluetoothProfile.LE_CALL_CONTROL:
+                BluetoothLeCallControl tbs = (BluetoothLeCallControl) proxy;
+                tbs.close();
+                break;
         }
     }
 
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index fc99942..984166d 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -23,8 +23,7 @@
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SuppressLint;
-import android.annotation.SystemApi;
-import android.app.PropertyInvalidatedCache;
+import android.annotation.SystemApi; //import android.app.PropertyInvalidatedCache;
 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
 import android.bluetooth.annotations.RequiresBluetoothLocationPermission;
 import android.bluetooth.annotations.RequiresBluetoothScanPermission;
@@ -1597,6 +1596,7 @@
         return false;
     }
 
+    /*
     private static final String BLUETOOTH_BONDING_CACHE_PROPERTY =
             "cache_key.bluetooth.get_bond_state";
     private final PropertyInvalidatedCache<BluetoothDevice, Integer> mBluetoothBondCache =
@@ -1612,16 +1612,19 @@
                     }
                 }
             };
+     */
 
     /** @hide */
-    public void disableBluetoothGetBondStateCache() {
+    /* public void disableBluetoothGetBondStateCache() {
         mBluetoothBondCache.disableLocal();
-    }
+    } */
 
     /** @hide */
+    /*
     public static void invalidateBluetoothGetBondStateCache() {
         PropertyInvalidatedCache.invalidateCache(BLUETOOTH_BONDING_CACHE_PROPERTY);
     }
+     */
 
     /**
      * Get the bond state of the remote device.
@@ -1643,13 +1646,11 @@
             return BOND_NONE;
         }
         try {
-            return mBluetoothBondCache.query(this);
-        } catch (RuntimeException e) {
-            if (e.getCause() instanceof RemoteException) {
-                Log.e(TAG, "", e);
-            } else {
-                throw e;
-            }
+            //return mBluetoothBondCache.query(this);
+            return sService.getBondState(this, mAttributionSource);
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to ", e);
+            e.rethrowFromSystemServer();
         }
         return BOND_NONE;
     }
diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
index 053e0db..c5e986e 100644
--- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java
+++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
@@ -313,7 +313,7 @@
     };
 
     private BluetoothGattCharacteristic(Parcel in) {
-        mUuid = ((ParcelUuid) in.readParcelable(null, android.os.ParcelUuid.class)).getUuid();
+        mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid();
         mInstance = in.readInt();
         mProperties = in.readInt();
         mPermissions = in.readInt();
diff --git a/core/java/android/bluetooth/BluetoothGattDescriptor.java b/core/java/android/bluetooth/BluetoothGattDescriptor.java
index 6ed4706..a35d5b9 100644
--- a/core/java/android/bluetooth/BluetoothGattDescriptor.java
+++ b/core/java/android/bluetooth/BluetoothGattDescriptor.java
@@ -187,7 +187,7 @@
     };
 
     private BluetoothGattDescriptor(Parcel in) {
-        mUuid = ((ParcelUuid) in.readParcelable(null, android.os.ParcelUuid.class)).getUuid();
+        mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid();
         mInstance = in.readInt();
         mPermissions = in.readInt();
     }
diff --git a/core/java/android/bluetooth/BluetoothGattIncludedService.java b/core/java/android/bluetooth/BluetoothGattIncludedService.java
index 1ae2ca0..5580619 100644
--- a/core/java/android/bluetooth/BluetoothGattIncludedService.java
+++ b/core/java/android/bluetooth/BluetoothGattIncludedService.java
@@ -76,7 +76,7 @@
     };
 
     private BluetoothGattIncludedService(Parcel in) {
-        mUuid = ((ParcelUuid) in.readParcelable(null, android.os.ParcelUuid.class)).getUuid();
+        mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid();
         mInstanceId = in.readInt();
         mServiceType = in.readInt();
     }
diff --git a/core/java/android/bluetooth/BluetoothGattService.java b/core/java/android/bluetooth/BluetoothGattService.java
index 36bc477..f64d09f 100644
--- a/core/java/android/bluetooth/BluetoothGattService.java
+++ b/core/java/android/bluetooth/BluetoothGattService.java
@@ -180,7 +180,7 @@
     };
 
     private BluetoothGattService(Parcel in) {
-        mUuid = ((ParcelUuid) in.readParcelable(null, android.os.ParcelUuid.class)).getUuid();
+        mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid();
         mInstanceId = in.readInt();
         mServiceType = in.readInt();
 
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index f2a6276..2ed1eb4 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -18,6 +18,7 @@
 
 import static android.bluetooth.BluetoothUtils.getSyncTimeout;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -44,6 +45,8 @@
 
 import com.android.modules.utils.SynchronousResultReceiver;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.TimeoutException;
@@ -895,17 +898,36 @@
                 com.android.internal.R.bool.config_bluetooth_sco_off_call);
     }
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
+            BluetoothHeadset.STATE_AUDIO_CONNECTING,
+            BluetoothHeadset.STATE_AUDIO_CONNECTED,
+            BluetoothStatusCodes.ERROR_TIMEOUT
+    })
+    public @interface GetAudioStateReturnValues {}
+
     /**
      * Get the current audio state of the Headset.
-     * Note: This is an internal function and shouldn't be exposed
+     *
+     * @param device is the Bluetooth device for which the audio state is being queried
+     * @return the audio state of the device or an error code
+     * @throws IllegalArgumentException if the device is null
      *
      * @hide
      */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @SystemApi
     @RequiresBluetoothConnectPermission
-    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
-    public int getAudioState(BluetoothDevice device) {
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
+    public @GetAudioStateReturnValues int getAudioState(@NonNull BluetoothDevice device) {
         if (VDBG) log("getAudioState");
+        if (device == null) {
+            throw new IllegalArgumentException("device cannot be null");
+        }
         final IBluetoothHeadset service = mService;
         final int defaultValue = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
         if (service == null) {
@@ -916,8 +938,12 @@
                 final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
                 service.getAudioState(device, mAttributionSource, recv);
                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
-            } catch (RemoteException | TimeoutException e) {
+            } catch (RemoteException e) {
                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+                throw e.rethrowFromSystemServer();
+            } catch (TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+                return BluetoothStatusCodes.ERROR_TIMEOUT;
             }
         }
         return defaultValue;
@@ -1005,103 +1031,112 @@
         }
     }
 
-    /**
-     * Check if at least one headset's SCO audio is connected or connecting
-     *
-     * @return true if at least one device's SCO audio is connected or connecting, false otherwise
-     * or on error
-     * @hide
-     */
-    @RequiresLegacyBluetoothPermission
-    @RequiresBluetoothConnectPermission
-    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
-    public boolean isAudioOn() {
-        if (VDBG) log("isAudioOn()");
-        final IBluetoothHeadset service = mService;
-        final boolean defaultValue = false;
-        if (service == null) {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) log(Log.getStackTraceString(new Throwable()));
-        } else if (isEnabled()) {
-            try {
-                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
-                service.isAudioOn(mAttributionSource, recv);
-                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
-            } catch (RemoteException | TimeoutException e) {
-                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
-            }
-        }
-        return defaultValue;
-    }
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            BluetoothStatusCodes.SUCCESS,
+            BluetoothStatusCodes.ERROR_UNKNOWN,
+            BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND,
+            BluetoothStatusCodes.ERROR_TIMEOUT,
+            BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_CONNECTED,
+            BluetoothStatusCodes.ERROR_NO_ACTIVE_DEVICES,
+            BluetoothStatusCodes.ERROR_NOT_ACTIVE_DEVICE,
+            BluetoothStatusCodes.ERROR_AUDIO_ROUTE_BLOCKED,
+            BluetoothStatusCodes.ERROR_CALL_ACTIVE,
+            BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED
+    })
+    public @interface ConnectAudioReturnValues {}
 
     /**
-     * Initiates a connection of headset audio to the current active device
+     * Initiates a connection of SCO audio to the current active HFP device. The active HFP device
+     * can be identified with {@link BluetoothAdapter#getActiveDevices(int)}.
+ * <p>
+     * If this function returns {@link BluetoothStatusCodes#SUCCESS}, the intent
+     * {@link #ACTION_AUDIO_STATE_CHANGED} will be broadcasted twice. First with {@link #EXTRA_STATE}
+     * set to {@link #STATE_AUDIO_CONNECTING}. This will be followed by a broadcast with
+     * {@link #EXTRA_STATE} set to either {@link #STATE_AUDIO_CONNECTED} if the audio connection is
+     * established or {@link #STATE_AUDIO_DISCONNECTED} if there was a failure in establishing the
+     * audio connection.
      *
-     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
-     * If this function returns true, this intent will be broadcasted with
-     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
-     *
-     * <p> {@link #EXTRA_STATE} will transition from
-     * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
-     * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
-     * in case of failure to establish the audio connection.
-     *
-     * Note that this intent will not be sent if {@link BluetoothHeadset#isAudioOn()} is true
-     * before calling this method
-     *
-     * @return false if there was some error such as there is no active headset
+     * @return whether the connection was successfully initiated or an error code on failure
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
     @RequiresBluetoothConnectPermission
-    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
-    public boolean connectAudio() {
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
+    public @ConnectAudioReturnValues int connectAudio() {
         if (VDBG) log("connectAudio()");
         final IBluetoothHeadset service = mService;
-        final boolean defaultValue = false;
+        final int defaultValue = BluetoothStatusCodes.ERROR_UNKNOWN;
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
         } else if (isEnabled()) {
             try {
-                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
                 service.connectAudio(mAttributionSource, recv);
                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
-            } catch (RemoteException | TimeoutException e) {
+            } catch (RemoteException e) {
                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+                throw e.rethrowFromSystemServer();
+            } catch (TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+                return BluetoothStatusCodes.ERROR_TIMEOUT;
             }
         }
         return defaultValue;
     }
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            BluetoothStatusCodes.SUCCESS,
+            BluetoothStatusCodes.ERROR_UNKNOWN,
+            BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND,
+            BluetoothStatusCodes.ERROR_TIMEOUT,
+            BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED,
+            BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_DISCONNECTED
+    })
+    public @interface DisconnectAudioReturnValues {}
+
     /**
-     * Initiates a disconnection of HFP SCO audio.
-     * Tear down voice recognition or virtual voice call if any.
+     * Initiates a disconnection of HFP SCO audio from actively connected devices. It also tears
+     * down voice recognition or virtual voice call, if any exists.
      *
-     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
-     * If this function returns true, this intent will be broadcasted with
-     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
+     * <p> If this function returns {@link BluetoothStatusCodes#SUCCESS}, the intent
+     * {@link #ACTION_AUDIO_STATE_CHANGED} will be broadcasted with {@link #EXTRA_STATE} set to
+     * {@link #STATE_AUDIO_DISCONNECTED}.
      *
-     * @return false if audio is not connected, or on error, true otherwise
+     * @return whether the disconnection was initiated successfully or an error code on failure
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
     @RequiresBluetoothConnectPermission
-    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
-    public boolean disconnectAudio() {
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
+    public @DisconnectAudioReturnValues int disconnectAudio() {
         if (VDBG) log("disconnectAudio()");
         final IBluetoothHeadset service = mService;
-        final boolean defaultValue = false;
+        final int defaultValue = BluetoothStatusCodes.ERROR_UNKNOWN;
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
         } else if (isEnabled()) {
             try {
-                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
                 service.disconnectAudio(mAttributionSource, recv);
                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
-            } catch (RemoteException | TimeoutException e) {
+            } catch (RemoteException e) {
                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+                throw e.rethrowFromSystemServer();
+            } catch (TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+                return BluetoothStatusCodes.ERROR_TIMEOUT;
             }
         }
         return defaultValue;
@@ -1386,7 +1421,10 @@
     @SystemApi
     @RequiresLegacyBluetoothPermission
     @RequiresBluetoothConnectPermission
-    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
     public boolean isInbandRingingEnabled() {
         if (DBG) log("isInbandRingingEnabled()");
         final IBluetoothHeadset service = mService;
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
index e9dd761..032b507 100644
--- a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
+++ b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
@@ -292,7 +292,7 @@
             new Parcelable.Creator<BluetoothHeadsetClientCall>() {
                 @Override
                 public BluetoothHeadsetClientCall createFromParcel(Parcel in) {
-                    return new BluetoothHeadsetClientCall((BluetoothDevice) in.readParcelable(null, android.bluetooth.BluetoothDevice.class),
+                    return new BluetoothHeadsetClientCall((BluetoothDevice) in.readParcelable(null),
                             in.readInt(), UUID.fromString(in.readString()), in.readInt(),
                             in.readString(), in.readInt() == 1, in.readInt() == 1,
                             in.readInt() == 1);
diff --git a/core/java/android/bluetooth/BluetoothLeCall.java b/core/java/android/bluetooth/BluetoothLeCall.java
new file mode 100644
index 0000000..fb7789d
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothLeCall.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2021 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * 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.bluetooth;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelUuid;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * Representation of Call
+ *
+ * @hide
+ */
+public final class BluetoothLeCall implements Parcelable {
+
+    /** @hide */
+    @IntDef(prefix = "STATE_", value = {
+            STATE_INCOMING,
+            STATE_DIALING,
+            STATE_ALERTING,
+            STATE_ACTIVE,
+            STATE_LOCALLY_HELD,
+            STATE_REMOTELY_HELD,
+            STATE_LOCALLY_AND_REMOTELY_HELD
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface State {
+    }
+
+    /**
+     * A remote party is calling (incoming call).
+     *
+     * @hide
+     */
+    public static final int STATE_INCOMING = 0x00;
+
+    /**
+     * The process to call the remote party has started but the remote party is not
+     * being alerted (outgoing call).
+     *
+     * @hide
+     */
+    public static final int STATE_DIALING = 0x01;
+
+    /**
+     * A remote party is being alerted (outgoing call).
+     *
+     * @hide
+     */
+    public static final int STATE_ALERTING = 0x02;
+
+    /**
+     * The call is in an active conversation.
+     *
+     * @hide
+     */
+    public static final int STATE_ACTIVE = 0x03;
+
+    /**
+     * The call is connected but held locally. “Locally Held” implies that either
+     * the server or the client can affect the state.
+     *
+     * @hide
+     */
+    public static final int STATE_LOCALLY_HELD = 0x04;
+
+    /**
+     * The call is connected but held remotely. “Remotely Held” means that the state
+     * is controlled by the remote party of a call.
+     *
+     * @hide
+     */
+    public static final int STATE_REMOTELY_HELD = 0x05;
+
+    /**
+     * The call is connected but held both locally and remotely.
+     *
+     * @hide
+     */
+    public static final int STATE_LOCALLY_AND_REMOTELY_HELD = 0x06;
+
+    /**
+     * Whether the call direction is outgoing.
+     *
+     * @hide
+     */
+    public static final int FLAG_OUTGOING_CALL = 0x00000001;
+
+    /**
+     * Whether the call URI and Friendly Name are withheld by server.
+     *
+     * @hide
+     */
+    public static final int FLAG_WITHHELD_BY_SERVER = 0x00000002;
+
+    /**
+     * Whether the call URI and Friendly Name are withheld by network.
+     *
+     * @hide
+     */
+    public static final int FLAG_WITHHELD_BY_NETWORK = 0x00000004;
+
+    /** Unique UUID that identifies this call */
+    private UUID mUuid;
+
+    /** Remote Caller URI */
+    private String mUri;
+
+    /** Caller friendly name */
+    private String mFriendlyName;
+
+    /** Call state */
+    private @State int mState;
+
+    /** Call flags */
+    private int mCallFlags;
+
+    /** @hide */
+    public BluetoothLeCall(@NonNull BluetoothLeCall that) {
+        mUuid = new UUID(that.getUuid().getMostSignificantBits(),
+                that.getUuid().getLeastSignificantBits());
+        mUri = that.mUri;
+        mFriendlyName = that.mFriendlyName;
+        mState = that.mState;
+        mCallFlags = that.mCallFlags;
+    }
+
+    /** @hide */
+    public BluetoothLeCall(@NonNull UUID uuid, @NonNull String uri, @NonNull String friendlyName,
+            @State int state, int callFlags) {
+        mUuid = uuid;
+        mUri = uri;
+        mFriendlyName = friendlyName;
+        mState = state;
+        mCallFlags = callFlags;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+        BluetoothLeCall that = (BluetoothLeCall) o;
+        return mUuid.equals(that.mUuid) && mUri.equals(that.mUri)
+                && mFriendlyName.equals(that.mFriendlyName) && mState == that.mState
+                && mCallFlags == that.mCallFlags;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mUuid, mUri, mFriendlyName, mState, mCallFlags);
+    }
+
+    /**
+     * Returns a string representation of this BluetoothLeCall.
+     *
+     * <p>
+     * Currently this is the UUID.
+     *
+     * @return string representation of this BluetoothLeCall
+     */
+    @Override
+    public String toString() {
+        return mUuid.toString();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeParcelable(new ParcelUuid(mUuid), 0);
+        out.writeString(mUri);
+        out.writeString(mFriendlyName);
+        out.writeInt(mState);
+        out.writeInt(mCallFlags);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<BluetoothLeCall> CREATOR =
+    						    new Parcelable.Creator<BluetoothLeCall>() {
+        public BluetoothLeCall createFromParcel(Parcel in) {
+            return new BluetoothLeCall(in);
+        }
+
+        public BluetoothLeCall[] newArray(int size) {
+            return new BluetoothLeCall[size];
+        }
+    };
+
+    private BluetoothLeCall(Parcel in) {
+        mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid();
+        mUri = in.readString();
+        mFriendlyName = in.readString();
+        mState = in.readInt();
+        mCallFlags = in.readInt();
+    }
+
+    /**
+     * Returns an UUID of this BluetoothLeCall.
+     *
+     * <p>
+     * An UUID is unique identifier of a BluetoothLeCall.
+     *
+     * @return UUID of this BluetoothLeCall
+     * @hide
+     */
+    public @NonNull UUID getUuid() {
+        return mUuid;
+    }
+
+    /**
+     * Returns a URI of the remote party of this BluetoothLeCall.
+     *
+     * @return string representation of this BluetoothLeCall
+     * @hide
+     */
+    public @NonNull String getUri() {
+        return mUri;
+    }
+
+    /**
+     * Returns a friendly name of the call.
+     *
+     * @return friendly name representation of this BluetoothLeCall
+     * @hide
+     */
+    public @NonNull String getFriendlyName() {
+        return mFriendlyName;
+    }
+
+    /**
+     * Returns the call state.
+     *
+     * @return the state of this BluetoothLeCall
+     * @hide
+     */
+    public @State int getState() {
+        return mState;
+    }
+
+    /**
+     * Returns the call flags.
+     *
+     * @return call flags
+     * @hide
+     */
+    public int getCallFlags() {
+        return mCallFlags;
+    }
+
+    /**
+     * Whether the call direction is incoming.
+     *
+     * @return true if incoming call, false otherwise
+     * @hide
+     */
+    public boolean isIncomingCall() {
+        return (mCallFlags & FLAG_OUTGOING_CALL) == 0;
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothLeCallControl.java b/core/java/android/bluetooth/BluetoothLeCallControl.java
new file mode 100644
index 0000000..fb080c9
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothLeCallControl.java
@@ -0,0 +1,899 @@
+/*
+ * Copyright 2019 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * 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.bluetooth;
+
+import android.Manifest;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.util.Log;
+import android.annotation.SuppressLint;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.Executor;
+
+/**
+ * This class provides the APIs to control the Call Control profile.
+ *
+ * <p>
+ * This class provides Bluetooth Telephone Bearer Service functionality,
+ * allowing applications to expose a GATT Service based interface to control the
+ * state of the calls by remote devices such as LE audio devices.
+ *
+ * <p>
+ * BluetoothLeCallControl is a proxy object for controlling the Bluetooth Telephone Bearer
+ * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the
+ * BluetoothLeCallControl proxy object.
+ *
+ * @hide
+ */
+public final class BluetoothLeCallControl implements BluetoothProfile {
+    private static final String TAG = "BluetoothLeCallControl";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    /** @hide */
+    @IntDef(prefix = "RESULT_", value = {
+            RESULT_SUCCESS,
+            RESULT_ERROR_UNKNOWN_CALL_ID,
+            RESULT_ERROR_INVALID_URI,
+            RESULT_ERROR_APPLICATION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Result {
+    }
+
+    /**
+     * Opcode write was successful.
+     *
+     * @hide
+     */
+    public static final int RESULT_SUCCESS = 0;
+
+    /**
+     * Unknown call Id has been used in the operation.
+     *
+     * @hide
+     */
+    public static final int RESULT_ERROR_UNKNOWN_CALL_ID = 1;
+
+    /**
+     * The URI provided in {@link Callback#onPlaceCallRequest} is invalid.
+     *
+     * @hide
+     */
+    public static final int RESULT_ERROR_INVALID_URI = 2;
+
+    /**
+     * Application internal error.
+     *
+     * @hide
+     */
+    public static final int RESULT_ERROR_APPLICATION = 3;
+
+    /** @hide */
+    @IntDef(prefix = "TERMINATION_REASON_", value = {
+            TERMINATION_REASON_INVALID_URI,
+            TERMINATION_REASON_FAIL,
+            TERMINATION_REASON_REMOTE_HANGUP,
+            TERMINATION_REASON_SERVER_HANGUP,
+            TERMINATION_REASON_LINE_BUSY,
+            TERMINATION_REASON_NETWORK_CONGESTION,
+            TERMINATION_REASON_CLIENT_HANGUP,
+            TERMINATION_REASON_NO_SERVICE,
+            TERMINATION_REASON_NO_ANSWER
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TerminationReason {
+    }
+
+    /**
+     * Remote Caller ID value used to place a call was formed improperly.
+     *
+     * @hide
+     */
+    public static final int TERMINATION_REASON_INVALID_URI = 0x00;
+
+    /**
+     * Call fail.
+     *
+     * @hide
+     */
+    public static final int TERMINATION_REASON_FAIL = 0x01;
+
+    /**
+     * Remote party ended call.
+     *
+     * @hide
+     */
+    public static final int TERMINATION_REASON_REMOTE_HANGUP = 0x02;
+
+    /**
+     * Call ended from the server.
+     *
+     * @hide
+     */
+    public static final int TERMINATION_REASON_SERVER_HANGUP = 0x03;
+
+    /**
+     * Line busy.
+     *
+     * @hide
+     */
+    public static final int TERMINATION_REASON_LINE_BUSY = 0x04;
+
+    /**
+     * Network congestion.
+     *
+     * @hide
+     */
+    public static final int TERMINATION_REASON_NETWORK_CONGESTION = 0x05;
+
+    /**
+     * Client terminated.
+     *
+     * @hide
+     */
+    public static final int TERMINATION_REASON_CLIENT_HANGUP = 0x06;
+
+    /**
+     * No service.
+     *
+     * @hide
+     */
+    public static final int TERMINATION_REASON_NO_SERVICE = 0x07;
+
+    /**
+     * No answer.
+     *
+     * @hide
+     */
+    public static final int TERMINATION_REASON_NO_ANSWER = 0x08;
+
+    /*
+     * Flag indicating support for hold/unhold call feature.
+     *
+     * @hide
+     */
+    public static final int CAPABILITY_HOLD_CALL = 0x00000001;
+
+    /**
+     * Flag indicating support for joining calls feature.
+     *
+     * @hide
+     */
+    public static final int CAPABILITY_JOIN_CALLS = 0x00000002;
+
+    private static final int MESSAGE_TBS_SERVICE_CONNECTED = 102;
+    private static final int MESSAGE_TBS_SERVICE_DISCONNECTED = 103;
+
+    private static final int REG_TIMEOUT = 10000;
+
+    /**
+     * The template class is used to call callback functions on events from the TBS
+     * server. Callback functions are wrapped in this class and registered to the
+     * Android system during app registration.
+     *
+     * @hide
+     */
+    public abstract static class Callback {
+
+        private static final String TAG = "BluetoothLeCallControl.Callback";
+
+        /**
+         * Called when a remote client requested to accept the call.
+         *
+         * <p>
+         * An application must call {@link BluetoothLeCallControl#requestResult} to complete the
+         * request.
+         *
+         * @param requestId The Id of the request
+         * @param callId    The call Id requested to be accepted
+         * @hide
+         */
+        public abstract void onAcceptCall(int requestId, @NonNull UUID callId);
+
+        /**
+         * A remote client has requested to terminate the call.
+         *
+         * <p>
+         * An application must call {@link BluetoothLeCallControl#requestResult} to complete the
+         * request.
+         *
+         * @param requestId The Id of the request
+         * @param callId    The call Id requested to terminate
+         * @hide
+         */
+        public abstract void onTerminateCall(int requestId, @NonNull UUID callId);
+
+        /**
+         * A remote client has requested to hold the call.
+         *
+         * <p>
+         * An application must call {@link BluetoothLeCallControl#requestResult} to complete the
+         * request.
+         *
+         * @param requestId The Id of the request
+         * @param callId    The call Id requested to be put on hold
+         * @hide
+         */
+        public void onHoldCall(int requestId, @NonNull UUID callId) {
+            Log.e(TAG, "onHoldCall: unimplemented, however CAPABILITY_HOLD_CALL is set!");
+        }
+
+        /**
+         * A remote client has requested to unhold the call.
+         *
+         * <p>
+         * An application must call {@link BluetoothLeCallControl#requestResult} to complete the
+         * request.
+         *
+         * @param requestId The Id of the request
+         * @param callId    The call Id requested to unhold
+         * @hide
+         */
+        public void onUnholdCall(int requestId, @NonNull UUID callId) {
+            Log.e(TAG, "onUnholdCall: unimplemented, however CAPABILITY_HOLD_CALL is set!");
+        }
+
+        /**
+         * A remote client has requested to place a call.
+         *
+         * <p>
+         * An application must call {@link BluetoothLeCallControl#requestResult} to complete the
+         * request.
+         *
+         * @param requestId The Id of the request
+         * @param callId    The Id to be assigned for the new call
+         * @param uri       The caller URI requested
+         * @hide
+         */
+        public abstract void onPlaceCall(int requestId, @NonNull UUID callId, @NonNull String uri);
+
+        /**
+         * A remote client has requested to join the calls.
+         *
+         * <p>
+         * An application must call {@link BluetoothLeCallControl#requestResult} to complete the
+         * request.
+         *
+         * @param requestId The Id of the request
+         * @param callIds   The call Id list requested to join
+         * @hide
+         */
+        public void onJoinCalls(int requestId, @NonNull List<UUID> callIds) {
+            Log.e(TAG, "onJoinCalls: unimplemented, however CAPABILITY_JOIN_CALLS is set!");
+        }
+    }
+
+    private class CallbackWrapper extends IBluetoothLeCallControlCallback.Stub {
+
+        private final Executor mExecutor;
+        private final Callback mCallback;
+
+        CallbackWrapper(Executor executor, Callback callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onBearerRegistered(int ccid) {
+            if (mCallback != null) {
+                mCcid = ccid;
+            } else {
+                // registration timeout
+                Log.e(TAG, "onBearerRegistered: mCallback is null");
+            }
+        }
+
+        @Override
+        public void onAcceptCall(int requestId, ParcelUuid uuid) {
+            final long identityToken = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mCallback.onAcceptCall(requestId, uuid.getUuid()));
+            } finally {
+                Binder.restoreCallingIdentity(identityToken);
+            }
+        }
+
+        @Override
+        public void onTerminateCall(int requestId, ParcelUuid uuid) {
+            final long identityToken = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mCallback.onTerminateCall(requestId, uuid.getUuid()));
+            } finally {
+                Binder.restoreCallingIdentity(identityToken);
+            }
+        }
+
+        @Override
+        public void onHoldCall(int requestId, ParcelUuid uuid) {
+            final long identityToken = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mCallback.onHoldCall(requestId, uuid.getUuid()));
+            } finally {
+                Binder.restoreCallingIdentity(identityToken);
+            }
+        }
+
+        @Override
+        public void onUnholdCall(int requestId, ParcelUuid uuid) {
+            final long identityToken = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mCallback.onUnholdCall(requestId, uuid.getUuid()));
+            } finally {
+                Binder.restoreCallingIdentity(identityToken);
+            }
+        }
+
+        @Override
+        public void onPlaceCall(int requestId, ParcelUuid uuid, String uri) {
+            final long identityToken = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mCallback.onPlaceCall(requestId, uuid.getUuid(), uri));
+            } finally {
+                Binder.restoreCallingIdentity(identityToken);
+            }
+        }
+
+        @Override
+        public void onJoinCalls(int requestId, List<ParcelUuid> parcelUuids) {
+            List<UUID> uuids = new ArrayList<>();
+            for (ParcelUuid parcelUuid : parcelUuids) {
+                uuids.add(parcelUuid.getUuid());
+            }
+
+            final long identityToken = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mCallback.onJoinCalls(requestId, uuids));
+            } finally {
+                Binder.restoreCallingIdentity(identityToken);
+            }
+        }
+    };
+
+    private Context mContext;
+    private ServiceListener mServiceListener;
+    private volatile IBluetoothLeCallControl mService;
+    private BluetoothAdapter mAdapter;
+    private int mCcid = 0;
+    private String mToken;
+    private Callback mCallback = null;
+
+    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+        new IBluetoothStateChangeCallback.Stub() {
+        public void onBluetoothStateChange(boolean up) {
+            if (DBG)
+                Log.d(TAG, "onBluetoothStateChange: up=" + up);
+            if (!up) {
+                doUnbind();
+            } else {
+                doBind();
+            }
+        }
+    };
+
+    /**
+     * Create a BluetoothLeCallControl proxy object for interacting with the local Bluetooth
+     * telephone bearer service.
+     */
+    /* package */ BluetoothLeCallControl(Context context, ServiceListener listener) {
+        mContext = context;
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mServiceListener = listener;
+
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException e) {
+                Log.e(TAG, "", e);
+            }
+        }
+
+        doBind();
+    }
+
+    private boolean doBind() {
+        synchronized (mConnection) {
+            if (mService == null) {
+                if (VDBG)
+                    Log.d(TAG, "Binding service...");
+                try {
+                    return mAdapter.getBluetoothManager().
+                            bindBluetoothProfileService(BluetoothProfile.LE_CALL_CONTROL,
+                            mConnection);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Unable to bind TelephoneBearerService", e);
+                }
+            }
+        }
+        return false;
+    }
+
+    private void doUnbind() {
+        synchronized (mConnection) {
+            if (mService != null) {
+                if (VDBG)
+                    Log.d(TAG, "Unbinding service...");
+                try {
+                    mAdapter.getBluetoothManager().
+                        unbindBluetoothProfileService(BluetoothProfile.LE_CALL_CONTROL,
+                        mConnection);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Unable to unbind TelephoneBearerService", e);
+                } finally {
+                    mService = null;
+                }
+            }
+        }
+    }
+
+    /* package */ void close() {
+        if (VDBG)
+            log("close()");
+        unregisterBearer();
+
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException re) {
+                Log.e(TAG, "", re);
+            }
+        }
+        mServiceListener = null;
+        doUnbind();
+    }
+
+    private IBluetoothLeCallControl getService() {
+        return mService;
+    }
+
+    /**
+     * Not supported
+     *
+     * @throws UnsupportedOperationException
+     */
+    @Override
+    public int getConnectionState(@Nullable BluetoothDevice device) {
+        throw new UnsupportedOperationException("not supported");
+    }
+
+    /**
+     * Not supported
+     *
+     * @throws UnsupportedOperationException
+     */
+    @Override
+    public @NonNull List<BluetoothDevice> getConnectedDevices() {
+        throw new UnsupportedOperationException("not supported");
+    }
+
+    /**
+     * Not supported
+     *
+     * @throws UnsupportedOperationException
+     */
+    @Override
+    public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(
+        @NonNull int[] states) {
+        throw new UnsupportedOperationException("not supported");
+    }
+
+    /**
+     * Register Telephone Bearer exposing the interface that allows remote devices
+     * to track and control the call states.
+     *
+     * <p>
+     * This is an asynchronous call. The callback is used to notify success or
+     * failure if the function returns true.
+     *
+     * <p>
+     * Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * <!-- The UCI is a String identifier of the telephone bearer as defined at
+     * https://www.bluetooth.com/specifications/assigned-numbers/uniform-caller-identifiers
+     * (login required). -->
+     *
+     * <!-- The examples of common URI schemes can be found in
+     * https://iana.org/assignments/uri-schemes/uri-schemes.xhtml -->
+     *
+     * <!-- The Technology is an integer value. The possible values are defined at
+     * https://www.bluetooth.com/specifications/assigned-numbers (login required).
+     * -->
+     *
+     * @param uci          Bearer Unique Client Identifier
+     * @param uriSchemes   URI Schemes supported list
+     * @param capabilities bearer capabilities
+     * @param provider     Network provider name
+     * @param technology   Network technology
+     * @param executor     {@link Executor} object on which callback will be
+     *                     executed. The Executor object is required.
+     * @param callback     {@link Callback} object to which callback messages will
+     *                     be sent. The Callback object is required.
+     * @return true on success, false otherwise
+     * @hide
+     */
+    @SuppressLint("ExecutorRegistration")
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean registerBearer(@Nullable String uci,
+                    @NonNull List<String> uriSchemes, int capabilities,
+                    @NonNull String provider, int technology,
+                    @NonNull Executor executor, @NonNull Callback callback) {
+        if (DBG) {
+            Log.d(TAG, "registerBearer");
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("null parameter: " + callback);
+        }
+        if (mCcid != 0) {
+            return false;
+        }
+
+        mToken = uci;
+
+        final IBluetoothLeCallControl service = getService();
+        if (service != null) {
+            if (mCallback != null) {
+                Log.e(TAG, "Bearer can be opened only once");
+                return false;
+            }
+
+            mCallback = callback;
+            try {
+                CallbackWrapper callbackWrapper = new CallbackWrapper(executor, callback);
+                service.registerBearer(mToken, callbackWrapper, uci, uriSchemes, capabilities,
+                                        provider, technology);
+            } catch (RemoteException e) {
+                Log.e(TAG, "", e);
+                mCallback = null;
+                return false;
+            }
+
+            if (mCcid == 0) {
+                mCallback = null;
+                return false;
+            }
+
+            return true;
+        }
+
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return false;
+    }
+
+    /**
+     * Unregister Telephone Bearer Service and destroy all the associated data.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public void unregisterBearer() {
+        if (DBG) {
+            Log.d(TAG, "unregisterBearer");
+        }
+        if (mCcid == 0) {
+            return;
+        }
+
+        int ccid = mCcid;
+        mCcid = 0;
+        mCallback = null;
+
+        final IBluetoothLeCallControl service = getService();
+        if (service != null) {
+            try {
+                service.unregisterBearer(mToken);
+            } catch (RemoteException e) {
+                Log.e(TAG, "", e);
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+    }
+
+    /**
+     * Get the Content Control ID (CCID) value.
+     *
+     * @return ccid Content Control ID value
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public int getContentControlId() {
+        return mCcid;
+    }
+
+    /**
+     * Notify about the newly added call.
+     *
+     * <p>
+     * This shall be called as early as possible after the call has been added.
+     *
+     * <p>
+     * Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param call Newly added call
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public void onCallAdded(@NonNull BluetoothLeCall call) {
+        if (DBG) {
+            Log.d(TAG, "onCallAdded: call=" + call);
+        }
+        if (mCcid == 0) {
+            return;
+        }
+
+        final IBluetoothLeCallControl service = getService();
+        if (service != null) {
+            try {
+                service.callAdded(mCcid, call);
+            } catch (RemoteException e) {
+                Log.e(TAG, "", e);
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+    }
+
+    /**
+     * Notify about the removed call.
+     *
+     * <p>
+     * This shall be called as early as possible after the call has been removed.
+     *
+     * <p>
+     * Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param callId The Id of a call that has been removed
+     * @param reason Call termination reason
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public void onCallRemoved(@NonNull UUID callId, @TerminationReason int reason) {
+        if (DBG) {
+            Log.d(TAG, "callRemoved: callId=" + callId);
+        }
+        if (mCcid == 0) {
+            return;
+        }
+
+        final IBluetoothLeCallControl service = getService();
+        if (service != null) {
+            try {
+                service.callRemoved(mCcid, new ParcelUuid(callId), reason);
+            } catch (RemoteException e) {
+                Log.e(TAG, "", e);
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+    }
+
+    /**
+     * Notify the call state change
+     *
+     * <p>
+     * This shall be called as early as possible after the state of the call has
+     * changed.
+     *
+     * <p>
+     * Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param callId The call Id that state has been changed
+     * @param state  Call state
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public void onCallStateChanged(@NonNull UUID callId, @BluetoothLeCall.State int state) {
+        if (DBG) {
+            Log.d(TAG, "callStateChanged: callId=" + callId + " state=" + state);
+        }
+        if (mCcid == 0) {
+            return;
+        }
+
+        final IBluetoothLeCallControl service = getService();
+        if (service != null) {
+            try {
+                service.callStateChanged(mCcid, new ParcelUuid(callId), state);
+            } catch (RemoteException e) {
+                Log.e(TAG, "", e);
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+    }
+
+    /**
+     * Provide the current calls list
+     *
+     * <p>
+     * This function must be invoked after registration if application has any
+     * calls.
+     *
+     * @param calls current calls list
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+     public void currentCallsList(@NonNull List<BluetoothLeCall> calls) {
+        final IBluetoothLeCallControl service = getService();
+        if (service != null) {
+            try {
+                service.currentCallsList(mCcid, calls);
+            } catch (RemoteException e) {
+                Log.e(TAG, "", e);
+            }
+        }
+    }
+
+    /**
+     * Provide the network current status
+     *
+     * <p>
+     * This function must be invoked on change of network state.
+     *
+     * <p>
+     * Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * <!-- The Technology is an integer value. The possible values are defined at
+     * https://www.bluetooth.com/specifications/assigned-numbers (login required).
+     * -->
+     *
+     * @param provider   Network provider name
+     * @param technology Network technology
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public void networkStateChanged(@NonNull String provider, int technology) {
+        if (DBG) {
+            Log.d(TAG, "networkStateChanged: provider=" + provider + ", technology=" + technology);
+        }
+        if (mCcid == 0) {
+            return;
+        }
+
+        final IBluetoothLeCallControl service = getService();
+        if (service != null) {
+            try {
+                service.networkStateChanged(mCcid, provider, technology);
+            } catch (RemoteException e) {
+                Log.e(TAG, "", e);
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+    }
+
+    /**
+     * Send a response to a call control request to a remote device.
+     *
+     * <p>
+     * This function must be invoked in when a request is received by one of these
+     * callback methods:
+     *
+     * <ul>
+     * <li>{@link Callback#onAcceptCall}
+     * <li>{@link Callback#onTerminateCall}
+     * <li>{@link Callback#onHoldCall}
+     * <li>{@link Callback#onUnholdCall}
+     * <li>{@link Callback#onPlaceCall}
+     * <li>{@link Callback#onJoinCalls}
+     * </ul>
+     *
+     * @param requestId The ID of the request that was received with the callback
+     * @param result    The result of the request to be sent to the remote devices
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public void requestResult(int requestId, @Result int result) {
+        if (DBG) {
+            Log.d(TAG, "requestResult: requestId=" + requestId + " result=" + result);
+        }
+        if (mCcid == 0) {
+            return;
+        }
+
+        final IBluetoothLeCallControl service = getService();
+        if (service != null) {
+            try {
+                service.requestResult(mCcid, requestId, result);
+            } catch (RemoteException e) {
+                Log.e(TAG, "", e);
+            }
+        }
+    }
+
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    private static boolean isValidDevice(@Nullable BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+
+    private final IBluetoothProfileServiceConnection mConnection =
+                                    new IBluetoothProfileServiceConnection.Stub() {
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            if (DBG) {
+                Log.d(TAG, "Proxy object connected");
+            }
+            mService = IBluetoothLeCallControl.Stub.asInterface(Binder.allowBlocking(service));
+            mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_TBS_SERVICE_CONNECTED));
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            if (DBG) {
+                Log.d(TAG, "Proxy object disconnected");
+            }
+            doUnbind();
+            mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_TBS_SERVICE_DISCONNECTED));
+        }
+    };
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+            case MESSAGE_TBS_SERVICE_CONNECTED: {
+                if (mServiceListener != null) {
+                    mServiceListener.onServiceConnected(BluetoothProfile.LE_CALL_CONTROL,
+                        BluetoothLeCallControl.this);
+                }
+                break;
+            }
+            case MESSAGE_TBS_SERVICE_DISCONNECTED: {
+                if (mServiceListener != null) {
+                    mServiceListener.onServiceDisconnected(BluetoothProfile.LE_CALL_CONTROL);
+                }
+                break;
+            }
+            }
+        }
+    };
+}
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index e047e5d..d0f74e9 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -240,12 +240,19 @@
     int LE_AUDIO_BROADCAST = 26;
 
     /**
+     * @hide
+     * Telephone Bearer Service from Call Control Profile
+     *
+     */
+    int LE_CALL_CONTROL = 27;
+
+    /**
      * Max profile ID. This value should be updated whenever a new profile is added to match
      * the largest value assigned to a profile.
      *
      * @hide
      */
-    int MAX_PROFILE_ID = 26;
+    int MAX_PROFILE_ID = 27;
 
     /**
      * Default priority for devices that we try to auto-connect to and
diff --git a/core/java/android/bluetooth/BluetoothStatusCodes.java b/core/java/android/bluetooth/BluetoothStatusCodes.java
index fff32ff..a8ce4b4 100644
--- a/core/java/android/bluetooth/BluetoothStatusCodes.java
+++ b/core/java/android/bluetooth/BluetoothStatusCodes.java
@@ -20,7 +20,7 @@
 
 /**
  * A class with constants representing possible return values for Bluetooth APIs. General return
- * values occupy the range 0 to 99. Profile-specific return values occupy the range 100-999.
+ * values occupy the range 0 to 199. Profile-specific return values occupy the range 200-999.
  * API-specific return values start at 1000. The exception to this is the "UNKNOWN" error code which
  * occupies the max integer value.
  */
@@ -29,28 +29,28 @@
     private BluetoothStatusCodes() {}
 
     /**
-     * Indicates that the API call was successful
+     * Indicates that the API call was successful.
      */
     public static final int SUCCESS = 0;
 
     /**
-     * Error code indicating that Bluetooth is not enabled
+     * Error code indicating that Bluetooth is not enabled.
      */
     public static final int ERROR_BLUETOOTH_NOT_ENABLED = 1;
 
     /**
      * Error code indicating that the API call was initiated by neither the system nor the active
-     * user
+     * user.
      */
     public static final int ERROR_BLUETOOTH_NOT_ALLOWED = 2;
 
     /**
-     * Error code indicating that the Bluetooth Device specified is not bonded
+     * Error code indicating that the Bluetooth Device specified is not bonded.
      */
     public static final int ERROR_DEVICE_NOT_BONDED = 3;
 
     /**
-     * Error code indicating that the Bluetooth Device specified is not connected, but is bonded
+     * Error code indicating that the Bluetooth Device specified is not connected, but is bonded.
      *
      * @hide
      */
@@ -58,7 +58,7 @@
 
     /**
      * Error code indicating that the caller does not have the
-     * {@link android.Manifest.permission#BLUETOOTH_ADVERTISE} permission
+     * {@link android.Manifest.permission#BLUETOOTH_ADVERTISE} permission.
      *
      * @hide
      */
@@ -66,13 +66,13 @@
 
     /**
      * Error code indicating that the caller does not have the
-     * {@link android.Manifest.permission#BLUETOOTH_CONNECT} permission
+     * {@link android.Manifest.permission#BLUETOOTH_CONNECT} permission.
      */
     public static final int ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION = 6;
 
     /**
      * Error code indicating that the caller does not have the
-     * {@link android.Manifest.permission#BLUETOOTH_SCAN} permission
+     * {@link android.Manifest.permission#BLUETOOTH_SCAN} permission.
      *
      * @hide
      */
@@ -80,30 +80,67 @@
 
     /**
      * Error code indicating that the caller does not have the
-     * {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission
+     * {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission.
      */
     public static final int ERROR_MISSING_BLUETOOTH_PRIVILEGED_PERMISSION = 8;
 
     /**
      * Error code indicating that the profile service is not bound. You can bind a profile service
-     * by calling {@link BluetoothAdapter#getProfileProxy}
+     * by calling {@link BluetoothAdapter#getProfileProxy}.
      */
     public static final int ERROR_PROFILE_SERVICE_NOT_BOUND = 9;
 
     /**
-     * Error code indicating that the feature is not supported.
+     * Indicates that the feature is supported.
      */
-    public static final int ERROR_FEATURE_NOT_SUPPORTED = 10;
+    public static final int FEATURE_SUPPORTED = 10;
+
+    /**
+     * Indicates that the feature is not supported.
+     */
+    public static final int FEATURE_NOT_SUPPORTED = 11;
+
+    /**
+     * Error code indicating that the device is not the active device for this profile.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ERROR_NOT_ACTIVE_DEVICE = 12;
+
+    /**
+     * Error code indicating that there are no active devices for the profile.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ERROR_NO_ACTIVE_DEVICES = 13;
+
+    /**
+     * Indicates that the Bluetooth profile is not connected to this device.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ERROR_PROFILE_NOT_CONNECTED = 14;
+
+    /**
+     * Error code indicating that the requested operation timed out.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ERROR_TIMEOUT = 15;
 
     /**
      * A GATT writeCharacteristic request is not permitted on the remote device.
      */
-    public static final int ERROR_GATT_WRITE_NOT_ALLOWED = 101;
+    public static final int ERROR_GATT_WRITE_NOT_ALLOWED = 200;
 
     /**
      * A GATT writeCharacteristic request is issued to a busy remote device.
      */
-    public static final int ERROR_GATT_WRITE_REQUEST_BUSY = 102;
+    public static final int ERROR_GATT_WRITE_REQUEST_BUSY = 201;
 
     /**
      * If another application has already requested {@link OobData} then another fetch will be
@@ -286,6 +323,38 @@
     public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_DISABLE_ENCRYPTION_FAILED = 1115;
 
     /**
+     * Indicates that there is already one device for which SCO audio is connected or connecting.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ERROR_AUDIO_DEVICE_ALREADY_CONNECTED = 1116;
+
+    /**
+     * Indicates that SCO audio was already not connected for this device.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ERROR_AUDIO_DEVICE_ALREADY_DISCONNECTED = 1117;
+
+    /**
+     * Indicates that there audio route is currently blocked by the system.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ERROR_AUDIO_ROUTE_BLOCKED = 1118;
+
+    /**
+     * Indicates that there is an active call preventing this operation from succeeding.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ERROR_CALL_ACTIVE = 1119;
+
+    /**
      * Indicates that an unknown error has occurred has occurred.
      */
     public static final int ERROR_UNKNOWN = Integer.MAX_VALUE;
diff --git a/core/java/android/bluetooth/BufferConstraints.java b/core/java/android/bluetooth/BufferConstraints.java
index 06b45ee..97d9723 100644
--- a/core/java/android/bluetooth/BufferConstraints.java
+++ b/core/java/android/bluetooth/BufferConstraints.java
@@ -55,7 +55,7 @@
     BufferConstraints(Parcel in) {
         mBufferConstraintList = new ArrayList<BufferConstraint>();
         mBufferConstraints = new HashMap<Integer, BufferConstraint>();
-        in.readList(mBufferConstraintList, BufferConstraint.class.getClassLoader(), android.bluetooth.BufferConstraint.class);
+        in.readList(mBufferConstraintList, BufferConstraint.class.getClassLoader());
         for (int i = 0; i < mBufferConstraintList.size(); i++) {
             mBufferConstraints.put(i, mBufferConstraintList.get(i));
         }
diff --git a/core/java/android/bluetooth/le/AdvertiseSettings.java b/core/java/android/bluetooth/le/AdvertiseSettings.java
index 7129d76..c52a6ee 100644
--- a/core/java/android/bluetooth/le/AdvertiseSettings.java
+++ b/core/java/android/bluetooth/le/AdvertiseSettings.java
@@ -16,6 +16,9 @@
 
 package android.bluetooth.le;
 
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.bluetooth.le.AdvertisingSetParameters.AddressTypeStatus;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -70,17 +73,21 @@
      */
     private static final int LIMITED_ADVERTISING_MAX_MILLIS = 180 * 1000;
 
+
     private final int mAdvertiseMode;
     private final int mAdvertiseTxPowerLevel;
     private final int mAdvertiseTimeoutMillis;
     private final boolean mAdvertiseConnectable;
+    private final int mOwnAddressType;
 
     private AdvertiseSettings(int advertiseMode, int advertiseTxPowerLevel,
-            boolean advertiseConnectable, int advertiseTimeout) {
+            boolean advertiseConnectable, int advertiseTimeout,
+            @AddressTypeStatus int ownAddressType) {
         mAdvertiseMode = advertiseMode;
         mAdvertiseTxPowerLevel = advertiseTxPowerLevel;
         mAdvertiseConnectable = advertiseConnectable;
         mAdvertiseTimeoutMillis = advertiseTimeout;
+        mOwnAddressType = ownAddressType;
     }
 
     private AdvertiseSettings(Parcel in) {
@@ -88,6 +95,7 @@
         mAdvertiseTxPowerLevel = in.readInt();
         mAdvertiseConnectable = in.readInt() != 0;
         mAdvertiseTimeoutMillis = in.readInt();
+        mOwnAddressType = in.readInt();
     }
 
     /**
@@ -118,12 +126,23 @@
         return mAdvertiseTimeoutMillis;
     }
 
+    /**
+     * @return the own address type for advertising
+     *
+     * @hide
+     */
+    @SystemApi
+    public @AddressTypeStatus int getOwnAddressType() {
+        return mOwnAddressType;
+    }
+
     @Override
     public String toString() {
         return "Settings [mAdvertiseMode=" + mAdvertiseMode
                 + ", mAdvertiseTxPowerLevel=" + mAdvertiseTxPowerLevel
                 + ", mAdvertiseConnectable=" + mAdvertiseConnectable
-                + ", mAdvertiseTimeoutMillis=" + mAdvertiseTimeoutMillis + "]";
+                + ", mAdvertiseTimeoutMillis=" + mAdvertiseTimeoutMillis
+                + ", mOwnAddressType=" + mOwnAddressType + "]";
     }
 
     @Override
@@ -137,6 +156,7 @@
         dest.writeInt(mAdvertiseTxPowerLevel);
         dest.writeInt(mAdvertiseConnectable ? 1 : 0);
         dest.writeInt(mAdvertiseTimeoutMillis);
+        dest.writeInt(mOwnAddressType);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<AdvertiseSettings> CREATOR =
@@ -160,6 +180,7 @@
         private int mTxPowerLevel = ADVERTISE_TX_POWER_MEDIUM;
         private int mTimeoutMillis = 0;
         private boolean mConnectable = true;
+        private int mOwnAddressType = AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT;
 
         /**
          * Set advertise mode to control the advertising power and latency.
@@ -226,10 +247,31 @@
         }
 
         /**
+         * Set own address type for advertising to control public or privacy mode. If used to set
+         * address type anything other than {@link AdvertisingSetParameters#ADDRESS_TYPE_DEFAULT},
+         * then it will require BLUETOOTH_PRIVILEGED permission and will be checked at the
+         * time of starting advertising.
+         *
+         * @throws IllegalArgumentException If the {@code ownAddressType} is invalid
+         *
+         * @hide
+         */
+        @SystemApi
+        public @NonNull Builder setOwnAddressType(@AddressTypeStatus int ownAddressType) {
+            if (ownAddressType < AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT
+                    ||  ownAddressType > AdvertisingSetParameters.ADDRESS_TYPE_RANDOM) {
+                throw new IllegalArgumentException("unknown address type " + ownAddressType);
+            }
+            mOwnAddressType = ownAddressType;
+            return this;
+        }
+
+        /**
          * Build the {@link AdvertiseSettings} object.
          */
         public AdvertiseSettings build() {
-            return new AdvertiseSettings(mMode, mTxPowerLevel, mConnectable, mTimeoutMillis);
+            return new AdvertiseSettings(mMode, mTxPowerLevel, mConnectable, mTimeoutMillis,
+                mOwnAddressType);
         }
     }
 }
diff --git a/core/java/android/bluetooth/le/AdvertisingSetParameters.java b/core/java/android/bluetooth/le/AdvertisingSetParameters.java
index e39b198..5c8fae6 100644
--- a/core/java/android/bluetooth/le/AdvertisingSetParameters.java
+++ b/core/java/android/bluetooth/le/AdvertisingSetParameters.java
@@ -16,11 +16,17 @@
 
 package android.bluetooth.le;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * The {@link AdvertisingSetParameters} provide a way to adjust advertising
  * preferences for each
@@ -97,6 +103,39 @@
      */
     private static final int LIMITED_ADVERTISING_MAX_MILLIS = 180 * 1000;
 
+    /** @hide */
+    @IntDef(prefix = "ADDRESS_TYPE_", value = {
+        ADDRESS_TYPE_DEFAULT,
+        ADDRESS_TYPE_PUBLIC,
+        ADDRESS_TYPE_RANDOM
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AddressTypeStatus {}
+
+    /**
+     * Advertise own address type that corresponds privacy settings of the device.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ADDRESS_TYPE_DEFAULT = -1;
+
+    /**
+     * Advertise own public address type.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ADDRESS_TYPE_PUBLIC = 0;
+
+    /**
+     * Generate and adverise own resolvable private address.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ADDRESS_TYPE_RANDOM = 1;
+
     private final boolean mIsLegacy;
     private final boolean mIsAnonymous;
     private final boolean mIncludeTxPower;
@@ -106,11 +145,12 @@
     private final boolean mScannable;
     private final int mInterval;
     private final int mTxPowerLevel;
+    private final int mOwnAddressType;
 
     private AdvertisingSetParameters(boolean connectable, boolean scannable, boolean isLegacy,
             boolean isAnonymous, boolean includeTxPower,
             int primaryPhy, int secondaryPhy,
-            int interval, int txPowerLevel) {
+            int interval, int txPowerLevel, @AddressTypeStatus int ownAddressType) {
         mConnectable = connectable;
         mScannable = scannable;
         mIsLegacy = isLegacy;
@@ -120,6 +160,7 @@
         mSecondaryPhy = secondaryPhy;
         mInterval = interval;
         mTxPowerLevel = txPowerLevel;
+        mOwnAddressType = ownAddressType;
     }
 
     private AdvertisingSetParameters(Parcel in) {
@@ -132,6 +173,7 @@
         mSecondaryPhy = in.readInt();
         mInterval = in.readInt();
         mTxPowerLevel = in.readInt();
+        mOwnAddressType = in.readInt();
     }
 
     /**
@@ -197,6 +239,16 @@
         return mTxPowerLevel;
     }
 
+    /**
+     * @return the own address type for advertising
+     *
+     * @hide
+     */
+    @SystemApi
+    public @AddressTypeStatus int getOwnAddressType() {
+        return mOwnAddressType;
+    }
+
     @Override
     public String toString() {
         return "AdvertisingSetParameters [connectable=" + mConnectable
@@ -206,7 +258,8 @@
                 + ", primaryPhy=" + mPrimaryPhy
                 + ", secondaryPhy=" + mSecondaryPhy
                 + ", interval=" + mInterval
-                + ", txPowerLevel=" + mTxPowerLevel + "]";
+                + ", txPowerLevel=" + mTxPowerLevel
+                + ", ownAddressType=" + mOwnAddressType + "]";
     }
 
     @Override
@@ -225,6 +278,7 @@
         dest.writeInt(mSecondaryPhy);
         dest.writeInt(mInterval);
         dest.writeInt(mTxPowerLevel);
+        dest.writeInt(mOwnAddressType);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<AdvertisingSetParameters> CREATOR =
@@ -253,6 +307,7 @@
         private int mSecondaryPhy = BluetoothDevice.PHY_LE_1M;
         private int mInterval = INTERVAL_LOW;
         private int mTxPowerLevel = TX_POWER_MEDIUM;
+        private int mOwnAddressType = ADDRESS_TYPE_DEFAULT;
 
         /**
          * Set whether the advertisement type should be connectable or
@@ -399,6 +454,26 @@
         }
 
         /**
+         * Set own address type for advertising to control public or privacy mode. If used to set
+         * address type anything other than {@link AdvertisingSetParameters#ADDRESS_TYPE_DEFAULT},
+         * then it will require BLUETOOTH_PRIVILEGED permission and will be checked at the
+         * time of starting advertising.
+         *
+         * @throws IllegalArgumentException If the {@code ownAddressType} is invalid
+         *
+         * @hide
+         */
+        @SystemApi
+        public @NonNull Builder setOwnAddressType(@AddressTypeStatus int ownAddressType) {
+            if (ownAddressType < AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT
+                    ||  ownAddressType > AdvertisingSetParameters.ADDRESS_TYPE_RANDOM) {
+                throw new IllegalArgumentException("unknown address type " + ownAddressType);
+            }
+            mOwnAddressType = ownAddressType;
+            return this;
+        }
+
+        /**
          * Build the {@link AdvertisingSetParameters} object.
          *
          * @throws IllegalStateException if invalid combination of parameters is used.
@@ -431,7 +506,8 @@
             }
 
             return new AdvertisingSetParameters(mConnectable, mScannable, mIsLegacy, mIsAnonymous,
-                    mIncludeTxPower, mPrimaryPhy, mSecondaryPhy, mInterval, mTxPowerLevel);
+                    mIncludeTxPower, mPrimaryPhy, mSecondaryPhy, mInterval, mTxPowerLevel,
+                    mOwnAddressType);
         }
     }
 }
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index b9f8a57..879dcee 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -138,6 +138,7 @@
             parameters.setLegacyMode(true);
             parameters.setConnectable(isConnectable);
             parameters.setScannable(true); // legacy advertisements we support are always scannable
+            parameters.setOwnAddressType(settings.getOwnAddressType());
             if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_POWER) {
                 parameters.setInterval(1600); // 1s
             } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_BALANCED) {
diff --git a/core/java/android/bluetooth/le/ScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java
index 675fe05..b059193 100644
--- a/core/java/android/bluetooth/le/ScanFilter.java
+++ b/core/java/android/bluetooth/le/ScanFilter.java
@@ -200,28 +200,28 @@
                 address = in.readString();
             }
             if (in.readInt() == 1) {
-                ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader(), android.os.ParcelUuid.class);
+                ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader());
                 builder.setServiceUuid(uuid);
                 if (in.readInt() == 1) {
                     ParcelUuid uuidMask = in.readParcelable(
-                            ParcelUuid.class.getClassLoader(), android.os.ParcelUuid.class);
+                            ParcelUuid.class.getClassLoader());
                     builder.setServiceUuid(uuid, uuidMask);
                 }
             }
             if (in.readInt() == 1) {
                 ParcelUuid solicitationUuid = in.readParcelable(
-                        ParcelUuid.class.getClassLoader(), android.os.ParcelUuid.class);
+                        ParcelUuid.class.getClassLoader());
                 builder.setServiceSolicitationUuid(solicitationUuid);
                 if (in.readInt() == 1) {
                     ParcelUuid solicitationUuidMask = in.readParcelable(
-                            ParcelUuid.class.getClassLoader(), android.os.ParcelUuid.class);
+                            ParcelUuid.class.getClassLoader());
                     builder.setServiceSolicitationUuid(solicitationUuid,
                             solicitationUuidMask);
                 }
             }
             if (in.readInt() == 1) {
                 ParcelUuid servcieDataUuid =
-                        in.readParcelable(ParcelUuid.class.getClassLoader(), android.os.ParcelUuid.class);
+                        in.readParcelable(ParcelUuid.class.getClassLoader());
                 if (in.readInt() == 1) {
                     int serviceDataLength = in.readInt();
                     byte[] serviceData = new byte[serviceDataLength];
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 1d2f06d..18a59d8 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -595,7 +595,7 @@
         boolean forceConfirmation = (flg & 0x20) != 0;
         boolean skipPrompt = (flg & 0x400) != 0;
         List<DeviceFilter<?>> deviceFilters = new ArrayList<>();
-        in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader(), (Class<android.companion.DeviceFilter<?>>) (Class<?>) android.companion.DeviceFilter.class);
+        in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader());
         String deviceProfile = (flg & 0x4) == 0 ? null : in.readString();
         CharSequence displayName = (flg & 0x8) == 0 ? null : (CharSequence) in.readCharSequence();
         String packageName = (flg & 0x40) == 0 ? null : in.readString();
diff --git a/core/java/android/companion/BluetoothDeviceFilter.java b/core/java/android/companion/BluetoothDeviceFilter.java
index e0018f4..be663f7 100644
--- a/core/java/android/companion/BluetoothDeviceFilter.java
+++ b/core/java/android/companion/BluetoothDeviceFilter.java
@@ -70,7 +70,7 @@
     }
 
     private static List<ParcelUuid> readUuids(Parcel in) {
-        return in.readParcelableList(new ArrayList<>(), ParcelUuid.class.getClassLoader(), android.os.ParcelUuid.class);
+        return in.readParcelableList(new ArrayList<>(), ParcelUuid.class.getClassLoader());
     }
 
     /** @hide */
diff --git a/core/java/android/companion/BluetoothLeDeviceFilter.java b/core/java/android/companion/BluetoothLeDeviceFilter.java
index e6091f0..58898cc 100644
--- a/core/java/android/companion/BluetoothLeDeviceFilter.java
+++ b/core/java/android/companion/BluetoothLeDeviceFilter.java
@@ -252,7 +252,7 @@
         public BluetoothLeDeviceFilter createFromParcel(Parcel in) {
             Builder builder = new Builder()
                     .setNamePattern(patternFromString(in.readString()))
-                    .setScanFilter(in.readParcelable(null, android.bluetooth.le.ScanFilter.class));
+                    .setScanFilter(in.readParcelable(null));
             byte[] rawDataFilter = in.createByteArray();
             byte[] rawDataFilterMask = in.createByteArray();
             if (rawDataFilter != null) {
diff --git a/core/java/android/companion/SystemDataTransferRequest.aidl b/core/java/android/companion/SystemDataTransferRequest.aidl
new file mode 100644
index 0000000..19ae60e
--- /dev/null
+++ b/core/java/android/companion/SystemDataTransferRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.companion;
+
+parcelable SystemDataTransferRequest;
diff --git a/core/java/android/companion/SystemDataTransferRequest.java b/core/java/android/companion/SystemDataTransferRequest.java
new file mode 100644
index 0000000..e3b0369
--- /dev/null
+++ b/core/java/android/companion/SystemDataTransferRequest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.companion;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.OneTimeUseBuilder;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A request for users to allow the companion app to transfer system data to the companion devices.
+ *
+ * @hide
+ */
+public final class SystemDataTransferRequest implements Parcelable {
+
+    private final int mAssociationId;
+    private final boolean mPermissionSyncAllPackages;
+    private final List<String> mPermissionSyncPackages;
+
+    /**
+     * @hide
+     */
+    public SystemDataTransferRequest(int associationId, boolean syncAllPackages,
+            @Nullable List<String> permissionSyncPackages) {
+        mAssociationId = associationId;
+        mPermissionSyncAllPackages = syncAllPackages;
+        mPermissionSyncPackages = permissionSyncPackages;
+    }
+
+    public int getAssociationId() {
+        return mAssociationId;
+    }
+
+    @NonNull
+    public boolean isPermissionSyncAllPackages() {
+        return mPermissionSyncAllPackages;
+    }
+
+    @NonNull
+    public List<String> getPermissionSyncPackages() {
+        return mPermissionSyncPackages;
+    }
+
+    /**
+     * A builder for {@link SystemDataTransferRequest}.
+     *
+     * <p>You have to call one of the below methods to create a valid request</p>
+     * <br>1. {@link #setPermissionSyncAllPackages()}
+     * <br>2. {@link #setPermissionSyncPackages(List)}
+     */
+    public static final class Builder extends OneTimeUseBuilder<SystemDataTransferRequest> {
+
+        private final int mAssociationId;
+        private boolean mPermissionSyncAllPackages;
+        private List<String> mPermissionSyncPackages = new ArrayList<>();
+
+        public Builder(int associationId) {
+            mAssociationId = associationId;
+        }
+
+        /**
+         * Call to sync permissions for all the packages. You can optionally call
+         * {@link #setPermissionSyncPackages(List)} to specify the packages to sync permissions.
+         *
+         * <p>The system will only sync permissions that are explicitly granted by the user.</p>
+         *
+         * <p>If a permission is granted or revoked by the system or a policy, even if the user has
+         * explicitly granted or revoked the permission earlier, the permission will be ignored.</p>
+         *
+         * <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
+         */
+        @NonNull
+        public Builder setPermissionSyncAllPackages() {
+            mPermissionSyncAllPackages = true;
+            return this;
+        }
+
+        /**
+         * 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
+         */
+        @NonNull
+        public Builder setPermissionSyncPackages(@NonNull List<String> permissionSyncPackages) {
+            mPermissionSyncPackages = permissionSyncPackages;
+            return this;
+        }
+
+        @Override
+        @NonNull
+        public SystemDataTransferRequest build() {
+            return new SystemDataTransferRequest(mAssociationId, mPermissionSyncAllPackages,
+                    mPermissionSyncPackages);
+        }
+    }
+
+    SystemDataTransferRequest(Parcel in) {
+        mAssociationId = in.readInt();
+        mPermissionSyncAllPackages = in.readBoolean();
+        mPermissionSyncPackages = Arrays.asList(in.createString8Array());
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mAssociationId);
+        dest.writeBoolean(mPermissionSyncAllPackages);
+        dest.writeString8Array(mPermissionSyncPackages.toArray(new String[0]));
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Creator<SystemDataTransferRequest> CREATOR =
+            new Creator<SystemDataTransferRequest>() {
+                @Override
+                public SystemDataTransferRequest createFromParcel(Parcel in) {
+                    return new SystemDataTransferRequest(in);
+                }
+
+                @Override
+                public SystemDataTransferRequest[] newArray(int size) {
+                    return new SystemDataTransferRequest[size];
+                }
+            };
+}
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 2dfa202..d80bee6 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -17,6 +17,7 @@
 package android.companion.virtual;
 
 import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.VirtualDeviceParams;
 
 /**
  * Interface for communication between VirtualDeviceManager and VirtualDeviceManagerService.
@@ -33,6 +34,10 @@
      *   that this belongs to the calling UID.
      * @param associationId The association ID as returned by {@link AssociationInfo#getId()} from
      *   CDM. Virtual devices must have a corresponding association with CDM in order to be created.
+     * @param params The parameters for creating this virtual device. See {@link
+     *   VirtualDeviceManager.VirtualDeviceParams}.
      */
-    IVirtualDevice createVirtualDevice(in IBinder token, String packageName, int associationId);
+    IVirtualDevice createVirtualDevice(
+            in IBinder token, String packageName, int associationId,
+            in VirtualDeviceParams params);
 }
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index bace45b..858e4daa1 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -100,11 +100,11 @@
      */
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     @Nullable
-    public VirtualDevice createVirtualDevice(int associationId) {
+    public VirtualDevice createVirtualDevice(int associationId, VirtualDeviceParams params) {
         // TODO(b/194949534): Unhide this API
         try {
             IVirtualDevice virtualDevice = mService.createVirtualDevice(
-                    new Binder(), mContext.getPackageName(), associationId);
+                    new Binder(), mContext.getPackageName(), associationId, params);
             return new VirtualDevice(mContext, virtualDevice);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -273,6 +273,12 @@
             }
         }
 
+        /**
+         * Returns the display flags that should be added to a particular virtual display.
+         * Additional device-level flags from {@link
+         * com.android.server.companion.virtual.VirtualDeviceImpl#getBaseVirtualDisplayFlags()} will
+         * be added by DisplayManagerService.
+         */
         private int getVirtualDisplayFlags(@DisplayFlags int flags) {
             int virtualDisplayFlags = DEFAULT_VIRTUAL_DISPLAY_FLAGS;
             if ((flags & DISPLAY_FLAG_TRUSTED) != 0) {
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.aidl b/core/java/android/companion/virtual/VirtualDeviceParams.aidl
new file mode 100644
index 0000000..9b3974a
--- /dev/null
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual;
+
+parcelable VirtualDeviceParams;
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
new file mode 100644
index 0000000..d61d474
--- /dev/null
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual;
+
+import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.util.ArraySet;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Params that can be configured when creating virtual devices.
+ *
+ * @hide
+ */
+// TODO(b/194949534): Unhide this API
+public final class VirtualDeviceParams implements Parcelable {
+
+    /** @hide */
+    @IntDef(prefix = "LOCK_STATE_",
+            value = {LOCK_STATE_ALWAYS_LOCKED, LOCK_STATE_ALWAYS_UNLOCKED})
+    @Retention(RetentionPolicy.SOURCE)
+    @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+    public @interface LockState {}
+
+    /**
+     * Indicates that the lock state of the virtual device should be always locked.
+     *
+     * @hide  // TODO(b/194949534): Unhide this API
+     */
+    public static final int LOCK_STATE_ALWAYS_LOCKED = 0;
+
+    /**
+     * Indicates that the lock state of the virtual device should be always unlocked.
+     *
+     * @hide  // TODO(b/194949534): Unhide this API
+     */
+    public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1;
+
+    private final int mLockState;
+    private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
+
+    private VirtualDeviceParams(
+            @LockState int lockState,
+            @NonNull Set<UserHandle> usersWithMatchingAccounts) {
+        mLockState = lockState;
+        mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts);
+    }
+
+    @SuppressWarnings("unchecked")
+    private VirtualDeviceParams(Parcel parcel) {
+        mLockState = parcel.readInt();
+        mUsersWithMatchingAccounts = (ArraySet<UserHandle>) parcel.readArraySet(null);
+    }
+
+    /**
+     * Returns the lock state of the virtual device.
+     */
+    @LockState
+    public int getLockState() {
+        return mLockState;
+    }
+
+    /**
+     * Returns the user handles with matching managed accounts on the remote device to which
+     * this virtual device is streaming.
+     *
+     * @see android.app.admin.DevicePolicyManager#NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY
+     */
+    @NonNull
+    public Set<UserHandle> getUsersWithMatchingAccounts() {
+        return Collections.unmodifiableSet(mUsersWithMatchingAccounts);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mLockState);
+        dest.writeArraySet(mUsersWithMatchingAccounts);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof VirtualDeviceParams)) {
+            return false;
+        }
+        VirtualDeviceParams that = (VirtualDeviceParams) o;
+        return mLockState == that.mLockState && mUsersWithMatchingAccounts.equals(
+                that.mUsersWithMatchingAccounts);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mLockState, mUsersWithMatchingAccounts);
+    }
+
+    @Override
+    public String toString() {
+        return "VirtualDeviceParams("
+                + " mLockState=" + mLockState
+                + " mUsersWithMatchingAccounts=" + mUsersWithMatchingAccounts
+                + ")";
+    }
+
+    public static final Parcelable.Creator<VirtualDeviceParams> CREATOR =
+            new Parcelable.Creator<VirtualDeviceParams>() {
+                public VirtualDeviceParams createFromParcel(Parcel in) {
+                    return new VirtualDeviceParams(in);
+                }
+
+                public VirtualDeviceParams[] newArray(int size) {
+                    return new VirtualDeviceParams[size];
+                }
+            };
+
+    /**
+     * Builder for {@link VirtualDeviceParams}.
+     */
+    public static final class Builder {
+
+        private @LockState int mLockState = LOCK_STATE_ALWAYS_LOCKED;
+        private Set<UserHandle> mUsersWithMatchingAccounts;
+
+        /**
+         * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
+         * is required if this is set to {@link #LOCK_STATE_ALWAYS_UNLOCKED}.
+         * The default is {@link #LOCK_STATE_ALWAYS_LOCKED}.
+         *
+         * @param lockState The lock state, either {@link #LOCK_STATE_ALWAYS_LOCKED} or
+         *   {@link #LOCK_STATE_ALWAYS_UNLOCKED}.
+         */
+        @RequiresPermission(value = ADD_ALWAYS_UNLOCKED_DISPLAY, conditional = true)
+        @NonNull
+        public Builder setLockState(@LockState int lockState) {
+            mLockState = lockState;
+            return this;
+        }
+
+        /**
+         * Sets the user handles with matching managed accounts on the remote device to which
+         * this virtual device is streaming.
+         *
+         * @param usersWithMatchingAccounts A set of user handles with matching managed
+         *   accounts on the remote device this is streaming to.
+         * @see android.app.admin.DevicePolicyManager#NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY
+         */
+        public Builder setUsersWithMatchingAccounts(
+                @NonNull Set<UserHandle> usersWithMatchingAccounts) {
+            mUsersWithMatchingAccounts = usersWithMatchingAccounts;
+            return this;
+        }
+
+        /**
+         * Builds the {@link VirtualDeviceParams} instance.
+         */
+        @NonNull
+        public VirtualDeviceParams build() {
+            if (mUsersWithMatchingAccounts == null) {
+                mUsersWithMatchingAccounts = Collections.emptySet();
+            }
+            return new VirtualDeviceParams(mLockState, mUsersWithMatchingAccounts);
+        }
+    }
+}
diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java
index 0c065d9b..30775b1 100644
--- a/core/java/android/content/ContentProviderOperation.java
+++ b/core/java/android/content/ContentProviderOperation.java
@@ -108,7 +108,7 @@
             mExtras = null;
         }
         mSelection = source.readInt() != 0 ? source.readString8() : null;
-        mSelectionArgs = source.readSparseArray(null, java.lang.Object.class);
+        mSelectionArgs = source.readSparseArray(null);
         mExpectedCount = source.readInt() != 0 ? source.readInt() : null;
         mYieldAllowed = source.readInt() != 0;
         mExceptionAllowed = source.readInt() != 0;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index bccfacf..2df6f9a 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -94,8 +94,11 @@
 import java.io.InputStream;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * Interface to global information about an application environment.  This is
@@ -6407,6 +6410,43 @@
             @Nullable String writePermission, int pid, int uid, @Intent.AccessUriMode int modeFlags,
             @Nullable String message);
 
+
+    /**
+     * Triggers the asynchronous revocation of a permission.
+     *
+     * @param permName The name of the permission to be revoked.
+     * @see #selfRevokePermissions(Collection)
+     */
+    public void selfRevokePermission(@NonNull String permName) {
+        selfRevokePermissions(Collections.singletonList(permName));
+    }
+
+    /**
+     * Triggers the revocation of one or more permissions for the calling package. A package is only
+     * able to revoke a permission under the following conditions:
+     * <ul>
+     * <li>Each permission in {@code permissions} must be granted to the calling package.
+     * <li>Each permission in {@code permissions} must be a runtime permission.
+     * </ul>
+     * <p>
+     * For every permission in {@code permissions}, the entire permission group it belongs to will
+     * be revoked. The revocation happens asynchronously and kills all processes running in the
+     * calling UID. It will be triggered once it is safe to do so. In particular, it will not be
+     * triggered as long as the package remains in the foreground, or has any active manifest
+     * components (e.g. when another app is accessing a content provider in the package).
+     * <p>
+     * If you want to revoke the permissions right away, you could call {@code System.exit()}, but
+     * this could affect other apps that are accessing your app at the moment. For example, apps
+     * accessing a content provider in your app will all crash.
+     *
+     * @param permissions Collection of permissions to be revoked.
+     * @see PackageManager#getGroupOfPlatformPermission(String, Executor, Consumer)
+     * @see PackageManager#getPlatformPermissionsForGroup(String, Executor, Consumer)
+     */
+    public void selfRevokePermissions(@NonNull Collection<String> permissions) {
+        throw new AbstractMethodError("Must be overridden in implementing class");
+    }
+
     /** @hide */
     @IntDef(flag = true, prefix = { "CONTEXT_" }, value = {
             CONTEXT_INCLUDE_CODE,
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 3a02004..805e499 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -53,6 +53,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -1015,6 +1016,11 @@
     }
 
     @Override
+    public void selfRevokePermissions(@NonNull Collection<String> permissions) {
+        mBase.selfRevokePermissions(permissions);
+    }
+
+    @Override
     public Context createPackageContext(String packageName, int flags)
         throws PackageManager.NameNotFoundException {
         return mBase.createPackageContext(packageName, flags);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index af84392..74c326d 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4391,9 +4391,9 @@
      * restored from (corresponds to {@link android.os.Build.VERSION#SDK_INT}). The first three
      * values are represented as strings, the fourth one as int.
      *
-     * <p>This broadcast is sent only for settings provider entries known to require special handling
-     * around restore time.  These entries are found in the BROADCAST_ON_RESTORE table within
-     * the provider's backup agent implementation.
+     * <p>This broadcast is sent only for settings provider entries known to require special
+     * handling around restore time to specific receivers. These entries are found in the
+     * BROADCAST_ON_RESTORE table within the provider's backup agent implementation.
      *
      * @see #EXTRA_SETTING_NAME
      * @see #EXTRA_SETTING_PREVIOUS_VALUE
@@ -4401,15 +4401,46 @@
      * @see #EXTRA_SETTING_RESTORED_FROM_SDK_INT
      * {@hide}
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @SuppressLint("ActionValue")
     public static final String ACTION_SETTING_RESTORED = "android.os.action.SETTING_RESTORED";
 
-    /** {@hide} */
+    /**
+     * String intent extra to be used with {@link ACTION_SETTING_RESTORED}.
+     * Contain the name of the restored setting.
+     * {@hide}
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @SuppressLint("ActionValue")
     public static final String EXTRA_SETTING_NAME = "setting_name";
-    /** {@hide} */
+
+    /**
+     * String intent extra to be used with {@link ACTION_SETTING_RESTORED}.
+     * Contain the value of the {@link EXTRA_SETTING_NAME} settings entry prior to the restore
+     * operation.
+     * {@hide}
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @SuppressLint("ActionValue")
     public static final String EXTRA_SETTING_PREVIOUS_VALUE = "previous_value";
-    /** {@hide} */
+
+    /**
+     * String intent extra to be used with {@link ACTION_SETTING_RESTORED}.
+     * Contain the value of the {@link EXTRA_SETTING_NAME} settings entry being restored.
+     * {@hide}
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @SuppressLint("ActionValue")
     public static final String EXTRA_SETTING_NEW_VALUE = "new_value";
-    /** {@hide} */
+
+    /**
+     * Int intent extra to be used with {@link ACTION_SETTING_RESTORED}.
+     * Contain the version of the SDK that the setting has been restored from (corresponds to
+     * {@link android.os.Build.VERSION#SDK_INT}).
+     * {@hide}
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @SuppressLint("ActionValue")
     public static final String EXTRA_SETTING_RESTORED_FROM_SDK_INT = "restored_from_sdk_int";
 
     /**
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 32827ae..b3435b1 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -1182,7 +1182,7 @@
         public int match(Uri data, boolean wildcardSupported) {
             String host = data.getHost();
             if (host == null) {
-                if (wildcardSupported && mWild) {
+                if (wildcardSupported && mWild && mHost.isEmpty()) {
                     // special case, if no host is provided, but the Authority is wildcard, match
                     return MATCH_CATEGORY_HOST;
                 } else {
diff --git a/core/java/android/content/PeriodicSync.java b/core/java/android/content/PeriodicSync.java
index 6830f5f..432e81b 100644
--- a/core/java/android/content/PeriodicSync.java
+++ b/core/java/android/content/PeriodicSync.java
@@ -84,7 +84,7 @@
     }
 
     private PeriodicSync(Parcel in) {
-        this.account = in.readParcelable(null, android.accounts.Account.class);
+        this.account = in.readParcelable(null);
         this.authority = in.readString();
         this.extras = in.readBundle();
         this.period = in.readLong();
diff --git a/core/java/android/content/RestrictionEntry.java b/core/java/android/content/RestrictionEntry.java
index 63fcb49..8fd41f2 100644
--- a/core/java/android/content/RestrictionEntry.java
+++ b/core/java/android/content/RestrictionEntry.java
@@ -505,7 +505,7 @@
         mChoiceValues = in.readStringArray();
         mCurrentValue = in.readString();
         mCurrentValues = in.readStringArray();
-        Parcelable[] parcelables = in.readParcelableArray(null);
+        Parcelable[] parcelables = in.readParcelableArray(null, RestrictionEntry.class);
         if (parcelables != null) {
             mRestrictions = new RestrictionEntry[parcelables.length];
             for (int i = 0; i < parcelables.length; i++) {
diff --git a/core/java/android/content/SyncInfo.java b/core/java/android/content/SyncInfo.java
index 57101be..017a92b 100644
--- a/core/java/android/content/SyncInfo.java
+++ b/core/java/android/content/SyncInfo.java
@@ -99,7 +99,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     SyncInfo(Parcel parcel) {
         authorityId = parcel.readInt();
-        account = parcel.readParcelable(Account.class.getClassLoader(), android.accounts.Account.class);
+        account = parcel.readParcelable(Account.class.getClassLoader());
         authority = parcel.readString();
         startTime = parcel.readLong();
     }
diff --git a/core/java/android/content/SyncRequest.java b/core/java/android/content/SyncRequest.java
index 83ce84e..e1e6f75 100644
--- a/core/java/android/content/SyncRequest.java
+++ b/core/java/android/content/SyncRequest.java
@@ -174,7 +174,7 @@
         mIsAuthority = (in.readInt() != 0);
         mIsExpedited = (in.readInt() != 0);
         mIsScheduledAsExpeditedJob = (in.readInt() != 0);
-        mAccountToSync = in.readParcelable(null, android.accounts.Account.class);
+        mAccountToSync = in.readParcelable(null);
         mAuthority = in.readString();
     }
 
diff --git a/core/java/android/content/UndoManager.java b/core/java/android/content/UndoManager.java
index b2979f3..87afbf8 100644
--- a/core/java/android/content/UndoManager.java
+++ b/core/java/android/content/UndoManager.java
@@ -777,7 +777,7 @@
             final int N = p.readInt();
             for (int i=0; i<N; i++) {
                 UndoOwner owner = mManager.restoreOwner(p);
-                UndoOperation op = (UndoOperation)p.readParcelable(loader, android.content.UndoOperation.class);
+                UndoOperation op = (UndoOperation)p.readParcelable(loader);
                 op.mOwner = owner;
                 mOperations.add(op);
             }
diff --git a/core/java/android/content/UriPermission.java b/core/java/android/content/UriPermission.java
index 7347761..d3a9cb8 100644
--- a/core/java/android/content/UriPermission.java
+++ b/core/java/android/content/UriPermission.java
@@ -47,7 +47,7 @@
 
     /** {@hide} */
     public UriPermission(Parcel in) {
-        mUri = in.readParcelable(null, android.net.Uri.class);
+        mUri = in.readParcelable(null);
         mModeFlags = in.readInt();
         mPersistedTime = in.readLong();
     }
diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java
index 868dab2..73be0ff 100644
--- a/core/java/android/content/om/OverlayManagerTransaction.java
+++ b/core/java/android/content/om/OverlayManagerTransaction.java
@@ -67,7 +67,7 @@
         mRequests = new ArrayList<>(size);
         for (int i = 0; i < size; i++) {
             final int request = source.readInt();
-            final OverlayIdentifier overlay = source.readParcelable(null, android.content.om.OverlayIdentifier.class);
+            final OverlayIdentifier overlay = source.readParcelable(null);
             final int userId = source.readInt();
             final Bundle extras = source.readBundle(null);
             mRequests.add(new Request(request, overlay, userId, extras));
diff --git a/core/java/android/content/pm/BaseParceledListSlice.java b/core/java/android/content/pm/BaseParceledListSlice.java
index 7bade74..1e0deff 100644
--- a/core/java/android/content/pm/BaseParceledListSlice.java
+++ b/core/java/android/content/pm/BaseParceledListSlice.java
@@ -50,10 +50,12 @@
      */
     private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes();
 
-    private final List<T> mList;
+    private List<T> mList;
 
     private int mInlineCountLimit = Integer.MAX_VALUE;
 
+    private boolean mHasBeenParceled = false;
+
     public BaseParceledListSlice(List<T> list) {
         mList = list;
     }
@@ -151,9 +153,17 @@
      * Write this to another Parcel. Note that this discards the internal Parcel
      * and should not be used anymore. This is so we can pass this to a Binder
      * where we won't have a chance to call recycle on this.
+     *
+     * This method can only be called once per BaseParceledListSlice to ensure that
+     * the referenced list can be cleaned up before the recipient cleans up the
+     * Binder reference.
      */
     @Override
     public void writeToParcel(Parcel dest, int flags) {
+        if (mHasBeenParceled) {
+            throw new IllegalStateException("Can't Parcel a ParceledListSlice more than once");
+        }
+        mHasBeenParceled = true;
         final int N = mList.size();
         final int callFlags = flags;
         dest.writeInt(N);
@@ -180,9 +190,17 @@
                             throws RemoteException {
                         if (code != FIRST_CALL_TRANSACTION) {
                             return super.onTransact(code, data, reply, flags);
+                        } else if (mList == null) {
+                            throw new IllegalArgumentException("Attempt to transfer null list, "
+                                    + "did transfer finish?");
                         }
                         int i = data.readInt();
-                        if (DEBUG) Log.d(TAG, "Writing more @" + i + " of " + N);
+
+                        if (DEBUG) {
+                            Log.d(TAG, "Writing more @" + i + " of " + N + " to "
+                                    + Binder.getCallingPid() + ", sender=" + this);
+                        }
+
                         while (i < N && reply.dataSize() < MAX_IPC_SIZE) {
                             reply.writeInt(1);
 
@@ -196,6 +214,9 @@
                         if (i < N) {
                             if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N);
                             reply.writeInt(0);
+                        } else {
+                            if (DEBUG) Log.d(TAG, "Transfer complete, clearing mList reference");
+                            mList = null;
                         }
                         return true;
                     }
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index 48b634e..11b2ea1 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -180,6 +180,7 @@
      * {@link #startMainActivity}, this can start any activity of the caller package, not just
      * the main activity.
      * The caller must have the {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES}
+     * or {@link android.Manifest.permission#START_CROSS_PROFILE_ACTIVITIES}
      * permission and both the caller and target user profiles must be in the same profile group.
      *
      * @param component The ComponentName of the activity to launch. It must be exported.
@@ -189,7 +190,9 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.INTERACT_ACROSS_PROFILES,
+            android.Manifest.permission.START_CROSS_PROFILE_ACTIVITIES})
     public void startActivity(@NonNull ComponentName component, @NonNull UserHandle targetUser) {
         try {
             mService.startActivityAsUser(mContext.getIApplicationThread(),
diff --git a/core/java/android/content/pm/InstallSourceInfo.java b/core/java/android/content/pm/InstallSourceInfo.java
index 84d2ca3..a45bf79 100644
--- a/core/java/android/content/pm/InstallSourceInfo.java
+++ b/core/java/android/content/pm/InstallSourceInfo.java
@@ -61,7 +61,7 @@
 
     private InstallSourceInfo(Parcel source) {
         mInitiatingPackageName = source.readString();
-        mInitiatingPackageSigningInfo = source.readParcelable(SigningInfo.class.getClassLoader(), android.content.pm.SigningInfo.class);
+        mInitiatingPackageSigningInfo = source.readParcelable(SigningInfo.class.getClassLoader());
         mOriginatingPackageName = source.readString();
         mInstallingPackageName = source.readString();
     }
diff --git a/core/java/android/content/pm/InstantAppInfo.java b/core/java/android/content/pm/InstantAppInfo.java
index d6cfb0e..24d6a07 100644
--- a/core/java/android/content/pm/InstantAppInfo.java
+++ b/core/java/android/content/pm/InstantAppInfo.java
@@ -65,7 +65,7 @@
         mLabelText = parcel.readCharSequence();
         mRequestedPermissions = parcel.readStringArray();
         mGrantedPermissions = parcel.createStringArray();
-        mApplicationInfo = parcel.readParcelable(null, android.content.pm.ApplicationInfo.class);
+        mApplicationInfo = parcel.readParcelable(null);
     }
 
     /**
diff --git a/core/java/android/content/pm/InstantAppIntentFilter.java b/core/java/android/content/pm/InstantAppIntentFilter.java
index 721b261..123d2ba 100644
--- a/core/java/android/content/pm/InstantAppIntentFilter.java
+++ b/core/java/android/content/pm/InstantAppIntentFilter.java
@@ -46,7 +46,7 @@
 
     InstantAppIntentFilter(Parcel in) {
         mSplitName = in.readString();
-        in.readList(mFilters, getClass().getClassLoader(), android.content.IntentFilter.class);
+        in.readList(mFilters, getClass().getClassLoader());
     }
 
     public String getSplitName() {
diff --git a/core/java/android/content/pm/InstantAppResolveInfo.java b/core/java/android/content/pm/InstantAppResolveInfo.java
index 6124638..9881564 100644
--- a/core/java/android/content/pm/InstantAppResolveInfo.java
+++ b/core/java/android/content/pm/InstantAppResolveInfo.java
@@ -140,7 +140,7 @@
             mFilters = Collections.emptyList();
             mVersionCode = -1;
         } else {
-            mDigest = in.readParcelable(null /*loader*/, android.content.pm.InstantAppResolveInfo.InstantAppDigest.class);
+            mDigest = in.readParcelable(null /*loader*/);
             mPackageName = in.readString();
             mFilters = new ArrayList<>();
             in.readTypedList(mFilters, InstantAppIntentFilter.CREATOR);
diff --git a/core/java/android/content/pm/LauncherActivityInfoInternal.java b/core/java/android/content/pm/LauncherActivityInfoInternal.java
index 46c415d..417f168 100644
--- a/core/java/android/content/pm/LauncherActivityInfoInternal.java
+++ b/core/java/android/content/pm/LauncherActivityInfoInternal.java
@@ -43,10 +43,10 @@
     }
 
     public LauncherActivityInfoInternal(Parcel source) {
-        mActivityInfo = source.readParcelable(ActivityInfo.class.getClassLoader(), android.content.pm.ActivityInfo.class);
+        mActivityInfo = source.readParcelable(ActivityInfo.class.getClassLoader());
         mComponentName = new ComponentName(mActivityInfo.packageName, mActivityInfo.name);
         mIncrementalStatesInfo = source.readParcelable(
-                IncrementalStatesInfo.class.getClassLoader(), android.content.pm.IncrementalStatesInfo.class);
+                IncrementalStatesInfo.class.getClassLoader());
     }
 
     public ComponentName getComponentName() {
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 495100b..d76dc78 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1754,11 +1754,11 @@
             installScenario = source.readInt();
             sizeBytes = source.readLong();
             appPackageName = source.readString();
-            appIcon = source.readParcelable(null, android.graphics.Bitmap.class);
+            appIcon = source.readParcelable(null);
             appLabel = source.readString();
-            originatingUri = source.readParcelable(null, android.net.Uri.class);
+            originatingUri = source.readParcelable(null);
             originatingUid = source.readInt();
-            referrerUri = source.readParcelable(null, android.net.Uri.class);
+            referrerUri = source.readParcelable(null);
             abiOverride = source.readString();
             volumeUuid = source.readString();
             grantedRuntimePermissions = source.readStringArray();
@@ -1770,7 +1770,7 @@
             forceQueryableOverride = source.readBoolean();
             requiredInstalledVersionCode = source.readLong();
             DataLoaderParamsParcel dataLoaderParamsParcel = source.readParcelable(
-                    DataLoaderParamsParcel.class.getClassLoader(), android.content.pm.DataLoaderParamsParcel.class);
+                    DataLoaderParamsParcel.class.getClassLoader());
             if (dataLoaderParamsParcel != null) {
                 dataLoaderParams = new DataLoaderParams(dataLoaderParamsParcel);
             }
@@ -2367,42 +2367,72 @@
         private static final int[] NO_SESSIONS = {};
 
         /** @hide */
-        @IntDef(prefix = { "STAGED_SESSION_" }, value = {
-                STAGED_SESSION_NO_ERROR,
-                STAGED_SESSION_VERIFICATION_FAILED,
-                STAGED_SESSION_ACTIVATION_FAILED,
-                STAGED_SESSION_UNKNOWN,
-                STAGED_SESSION_CONFLICT})
+        @IntDef(prefix = { "SESSION_" }, value = {
+                SESSION_NO_ERROR,
+                SESSION_VERIFICATION_FAILED,
+                SESSION_ACTIVATION_FAILED,
+                SESSION_UNKNOWN_ERROR,
+                SESSION_CONFLICT})
         @Retention(RetentionPolicy.SOURCE)
         public @interface SessionErrorCode {}
         /**
-         * Constant indicating that no error occurred during the preparation or the activation of
-         * this staged session.
+         * @deprecated use {@link #SESSION_NO_ERROR}.
          */
+        @Deprecated
         public static final int STAGED_SESSION_NO_ERROR = 0;
 
         /**
-         * Constant indicating that an error occurred during the verification phase (pre-reboot) of
-         * this staged session.
+         * @deprecated use {@link #SESSION_VERIFICATION_FAILED}.
          */
+        @Deprecated
         public static final int STAGED_SESSION_VERIFICATION_FAILED = 1;
 
         /**
-         * Constant indicating that an error occurred during the activation phase (post-reboot) of
-         * this staged session.
+         * @deprecated use {@link #SESSION_ACTIVATION_FAILED}.
          */
+        @Deprecated
         public static final int STAGED_SESSION_ACTIVATION_FAILED = 2;
 
         /**
-         * Constant indicating that an unknown error occurred while processing this staged session.
+         * @deprecated use {@link #SESSION_UNKNOWN_ERROR}.
          */
+        @Deprecated
         public static final int STAGED_SESSION_UNKNOWN = 3;
 
         /**
-         * Constant indicating that the session was in conflict with another staged session and had
+         * @deprecated use {@link #SESSION_CONFLICT}.
+         */
+        @Deprecated
+        public static final int STAGED_SESSION_CONFLICT = 4;
+
+        /**
+         * Constant indicating that no error occurred during the preparation or the activation of
+         * this session.
+         */
+        public static final int SESSION_NO_ERROR = 0;
+
+        /**
+         * Constant indicating that an error occurred during the verification phase of
+         * this session.
+         */
+        public static final int SESSION_VERIFICATION_FAILED = 1;
+
+        /**
+         * Constant indicating that an error occurred during the activation phase of
+         * this session.
+         */
+        public static final int SESSION_ACTIVATION_FAILED = 2;
+
+        /**
+         * Constant indicating that an unknown error occurred while processing this session.
+         */
+        public static final int SESSION_UNKNOWN_ERROR = 3;
+
+        /**
+         * Constant indicating that the session was in conflict with another session and had
          * to be sacrificed for resolution.
          */
-        public static final int STAGED_SESSION_CONFLICT = 4;
+        public static final int SESSION_CONFLICT = 4;
 
         private static String userActionToString(int requireUserAction) {
             switch(requireUserAction) {
@@ -2533,13 +2563,13 @@
             installScenario = source.readInt();
             sizeBytes = source.readLong();
             appPackageName = source.readString();
-            appIcon = source.readParcelable(null, android.graphics.Bitmap.class);
+            appIcon = source.readParcelable(null);
             appLabel = source.readString();
 
             installLocation = source.readInt();
-            originatingUri = source.readParcelable(null, android.net.Uri.class);
+            originatingUri = source.readParcelable(null);
             originatingUid = source.readInt();
-            referrerUri = source.readParcelable(null, android.net.Uri.class);
+            referrerUri = source.readParcelable(null);
             grantedRuntimePermissions = source.readStringArray();
             whitelistedRestrictedPermissions = source.createStringArrayList();
             autoRevokePermissionsMode = source.readInt();
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index f31f78f..e2c91a4b 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -7297,7 +7297,7 @@
             splitFlags = dest.createIntArray();
             splitPrivateFlags = dest.createIntArray();
             baseHardwareAccelerated = (dest.readInt() == 1);
-            applicationInfo = dest.readParcelable(boot, android.content.pm.ApplicationInfo.class);
+            applicationInfo = dest.readParcelable(boot);
             if (applicationInfo.permission != null) {
                 applicationInfo.permission = applicationInfo.permission.intern();
             }
@@ -7305,19 +7305,19 @@
             // We don't serialize the "owner" package and the application info object for each of
             // these components, in order to save space and to avoid circular dependencies while
             // serialization. We need to fix them all up here.
-            dest.readParcelableList(permissions, boot, android.content.pm.PackageParser.Permission.class);
+            dest.readParcelableList(permissions, boot);
             fixupOwner(permissions);
-            dest.readParcelableList(permissionGroups, boot, android.content.pm.PackageParser.PermissionGroup.class);
+            dest.readParcelableList(permissionGroups, boot);
             fixupOwner(permissionGroups);
-            dest.readParcelableList(activities, boot, android.content.pm.PackageParser.Activity.class);
+            dest.readParcelableList(activities, boot);
             fixupOwner(activities);
-            dest.readParcelableList(receivers, boot, android.content.pm.PackageParser.Activity.class);
+            dest.readParcelableList(receivers, boot);
             fixupOwner(receivers);
-            dest.readParcelableList(providers, boot, android.content.pm.PackageParser.Provider.class);
+            dest.readParcelableList(providers, boot);
             fixupOwner(providers);
-            dest.readParcelableList(services, boot, android.content.pm.PackageParser.Service.class);
+            dest.readParcelableList(services, boot);
             fixupOwner(services);
-            dest.readParcelableList(instrumentation, boot, android.content.pm.PackageParser.Instrumentation.class);
+            dest.readParcelableList(instrumentation, boot);
             fixupOwner(instrumentation);
 
             dest.readStringList(requestedPermissions);
@@ -7327,10 +7327,10 @@
             protectedBroadcasts = dest.createStringArrayList();
             internStringArrayList(protectedBroadcasts);
 
-            parentPackage = dest.readParcelable(boot, android.content.pm.PackageParser.Package.class);
+            parentPackage = dest.readParcelable(boot);
 
             childPackages = new ArrayList<>();
-            dest.readParcelableList(childPackages, boot, android.content.pm.PackageParser.Package.class);
+            dest.readParcelableList(childPackages, boot);
             if (childPackages.size() == 0) {
                 childPackages = null;
             }
@@ -7364,7 +7364,7 @@
             }
 
             preferredActivityFilters = new ArrayList<>();
-            dest.readParcelableList(preferredActivityFilters, boot, android.content.pm.PackageParser.ActivityIntentInfo.class);
+            dest.readParcelableList(preferredActivityFilters, boot);
             if (preferredActivityFilters.size() == 0) {
                 preferredActivityFilters = null;
             }
@@ -7385,7 +7385,7 @@
             }
             mSharedUserLabel = dest.readInt();
 
-            mSigningDetails = dest.readParcelable(boot, android.content.pm.PackageParser.SigningDetails.class);
+            mSigningDetails = dest.readParcelable(boot);
 
             mPreferredOrder = dest.readInt();
 
@@ -7397,19 +7397,19 @@
 
 
             configPreferences = new ArrayList<>();
-            dest.readParcelableList(configPreferences, boot, android.content.pm.ConfigurationInfo.class);
+            dest.readParcelableList(configPreferences, boot);
             if (configPreferences.size() == 0) {
                 configPreferences = null;
             }
 
             reqFeatures = new ArrayList<>();
-            dest.readParcelableList(reqFeatures, boot, android.content.pm.FeatureInfo.class);
+            dest.readParcelableList(reqFeatures, boot);
             if (reqFeatures.size() == 0) {
                 reqFeatures = null;
             }
 
             featureGroups = new ArrayList<>();
-            dest.readParcelableList(featureGroups, boot, android.content.pm.FeatureGroupInfo.class);
+            dest.readParcelableList(featureGroups, boot);
             if (featureGroups.size() == 0) {
                 featureGroups = null;
             }
@@ -7806,13 +7806,13 @@
         private Permission(Parcel in) {
             super(in);
             final ClassLoader boot = Object.class.getClassLoader();
-            info = in.readParcelable(boot, android.content.pm.PermissionInfo.class);
+            info = in.readParcelable(boot);
             if (info.group != null) {
                 info.group = info.group.intern();
             }
 
             tree = (in.readInt() == 1);
-            group = in.readParcelable(boot, android.content.pm.PackageParser.PermissionGroup.class);
+            group = in.readParcelable(boot);
         }
 
         public static final Parcelable.Creator CREATOR = new Parcelable.Creator<Permission>() {
@@ -7867,7 +7867,7 @@
 
         private PermissionGroup(Parcel in) {
             super(in);
-            info = in.readParcelable(Object.class.getClassLoader(), android.content.pm.PermissionGroupInfo.class);
+            info = in.readParcelable(Object.class.getClassLoader());
         }
 
         public static final Parcelable.Creator CREATOR = new Parcelable.Creator<PermissionGroup>() {
@@ -8160,7 +8160,7 @@
 
         private Activity(Parcel in) {
             super(in);
-            info = in.readParcelable(Object.class.getClassLoader(), android.content.pm.ActivityInfo.class);
+            info = in.readParcelable(Object.class.getClassLoader());
             mHasMaxAspectRatio = in.readBoolean();
             mHasMinAspectRatio = in.readBoolean();
 
@@ -8254,7 +8254,7 @@
 
         private Service(Parcel in) {
             super(in);
-            info = in.readParcelable(Object.class.getClassLoader(), android.content.pm.ServiceInfo.class);
+            info = in.readParcelable(Object.class.getClassLoader());
 
             for (ServiceIntentInfo aii : intents) {
                 aii.service = this;
@@ -8344,7 +8344,7 @@
 
         private Provider(Parcel in) {
             super(in);
-            info = in.readParcelable(Object.class.getClassLoader(), android.content.pm.ProviderInfo.class);
+            info = in.readParcelable(Object.class.getClassLoader());
             syncable = (in.readInt() == 1);
 
             for (ProviderIntentInfo aii : intents) {
@@ -8436,7 +8436,7 @@
 
         private Instrumentation(Parcel in) {
             super(in);
-            info = in.readParcelable(Object.class.getClassLoader(), android.content.pm.InstrumentationInfo.class);
+            info = in.readParcelable(Object.class.getClassLoader());
 
             if (info.targetPackage != null) {
                 info.targetPackage = info.targetPackage.intern();
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 7696cbe..78984bd 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -174,7 +174,7 @@
 
         // Register for user-related events
         IntentFilter userFilter = new IntentFilter();
-        sdFilter.addAction(Intent.ACTION_USER_REMOVED);
+        userFilter.addAction(Intent.ACTION_USER_REMOVED);
         mContext.registerReceiver(mUserRemovedReceiver, userFilter, null, handler);
     }
 
diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java
index 43a4b17..f153566 100644
--- a/core/java/android/content/pm/SharedLibraryInfo.java
+++ b/core/java/android/content/pm/SharedLibraryInfo.java
@@ -136,8 +136,8 @@
         mName = parcel.readString8();
         mVersion = parcel.readLong();
         mType = parcel.readInt();
-        mDeclaringPackage = parcel.readParcelable(null, android.content.pm.VersionedPackage.class);
-        mDependentPackages = parcel.readArrayList(null, android.content.pm.VersionedPackage.class);
+        mDeclaringPackage = parcel.readParcelable(null);
+        mDependentPackages = parcel.readArrayList(null);
         mDependencies = parcel.createTypedArrayList(SharedLibraryInfo.CREATOR);
         mIsNative = parcel.readBoolean();
     }
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 7d4f7ec..613fb84 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -2182,7 +2182,7 @@
         mUserId = source.readInt();
         mId = source.readString8();
         mPackageName = source.readString8();
-        mActivity = source.readParcelable(cl, android.content.ComponentName.class);
+        mActivity = source.readParcelable(cl);
         mFlags = source.readInt();
         mIconResId = source.readInt();
         mLastChangedTimestamp = source.readLong();
@@ -2192,7 +2192,7 @@
             return; // key information only.
         }
 
-        mIcon = source.readParcelable(cl, android.graphics.drawable.Icon.class);
+        mIcon = source.readParcelable(cl);
         mTitle = source.readCharSequence();
         mTitleResId = source.readInt();
         mText = source.readCharSequence();
@@ -2202,7 +2202,7 @@
         mIntents = source.readParcelableArray(cl, Intent.class);
         mIntentPersistableExtrases = source.readParcelableArray(cl, PersistableBundle.class);
         mRank = source.readInt();
-        mExtras = source.readParcelable(cl, android.os.PersistableBundle.class);
+        mExtras = source.readParcelable(cl);
         mBitmapPath = source.readString8();
 
         mIconResName = source.readString8();
@@ -2221,7 +2221,7 @@
         }
 
         mPersons = source.readParcelableArray(cl, Person.class);
-        mLocusId = source.readParcelable(cl, android.content.LocusId.class);
+        mLocusId = source.readParcelable(cl);
         mIconUri = source.readString8();
         mStartingThemeResName = source.readString8();
         mExcludedSurfaces = source.readInt();
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 7dbfd08..be0d934 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -704,8 +704,8 @@
         }
 
         private ShareShortcutInfo(@NonNull Parcel in) {
-            mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader(), android.content.pm.ShortcutInfo.class);
-            mTargetComponent = in.readParcelable(ComponentName.class.getClassLoader(), android.content.ComponentName.class);
+            mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader());
+            mTargetComponent = in.readParcelable(ComponentName.class.getClassLoader());
         }
 
         @NonNull
diff --git a/core/java/android/content/pm/ShortcutQueryWrapper.java b/core/java/android/content/pm/ShortcutQueryWrapper.java
index 64337d8..c613441 100644
--- a/core/java/android/content/pm/ShortcutQueryWrapper.java
+++ b/core/java/android/content/pm/ShortcutQueryWrapper.java
@@ -143,7 +143,7 @@
         List<LocusId> locusIds = null;
         if ((flg & 0x8) != 0) {
             locusIds = new ArrayList<>();
-            in.readParcelableList(locusIds, LocusId.class.getClassLoader(), android.content.LocusId.class);
+            in.readParcelableList(locusIds, LocusId.class.getClassLoader());
         }
         ComponentName activity = (flg & 0x10) == 0 ? null
                 : (ComponentName) in.readTypedObject(ComponentName.CREATOR);
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index 77b8be3..df3466f 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -25,6 +25,9 @@
       "path": "cts/hostsidetests/packagemanager"
     },
     {
+      "path": "cts/hostsidetests/os/test_mappings/packagemanager"
+    },
+    {
       "path": "system/apex/tests"
     }
   ],
@@ -37,6 +40,9 @@
       "name": "CarrierAppIntegrationTestCases"
     },
     {
+      "name": "ApkVerityTest"
+    },
+    {
       "name": "CtsIncrementalInstallHostTestCases",
       "options": [
         {
@@ -135,6 +141,9 @@
           "include-filter": "android.appsecurity.cts.AppSecurityTests#testPermissionDiffCert"
         }
       ]
+    },
+    {
+      "name": "CtsInstallHostTestCases"
     }
   ]
 }
diff --git a/core/java/android/content/pm/VerifierInfo.java b/core/java/android/content/pm/VerifierInfo.java
index 868bb9c..3e69ff5 100644
--- a/core/java/android/content/pm/VerifierInfo.java
+++ b/core/java/android/content/pm/VerifierInfo.java
@@ -59,7 +59,7 @@
 
     private VerifierInfo(Parcel source) {
         packageName = source.readString();
-        publicKey = (PublicKey) source.readSerializable(java.security.PublicKey.class.getClassLoader(), java.security.PublicKey.class);
+        publicKey = (PublicKey) source.readSerializable();
     }
 
     @Override
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index 23cae4c..ddab207 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -1408,7 +1408,7 @@
         this.processes = in.readHashMap(boot);
         this.metaData = in.readBundle(boot);
         this.volumeUuid = sForInternedString.unparcel(in);
-        this.signingDetails = in.readParcelable(boot, android.content.pm.SigningDetails.class);
+        this.signingDetails = in.readParcelable(boot);
         this.mPath = in.readString();
         this.queriesIntents = in.createTypedArrayList(Intent.CREATOR);
         this.queriesPackages = sForInternedStringList.unparcel(in);
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index f336672..e02eb7c 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -3519,7 +3519,7 @@
 
             ArraySet<PublicKey> keys = new ArraySet<>(M);
             for (int j = 0; j < M; ++j) {
-                PublicKey pk = (PublicKey) in.readSerializable(java.security.PublicKey.class.getClassLoader(), java.security.PublicKey.class);
+                PublicKey pk = (PublicKey) in.readSerializable();
                 keys.add(pk);
             }
 
diff --git a/core/java/android/content/pm/parsing/ParsingUtils.java b/core/java/android/content/pm/parsing/ParsingUtils.java
index 6dfb268..cce984e 100644
--- a/core/java/android/content/pm/parsing/ParsingUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingUtils.java
@@ -138,7 +138,7 @@
             final List<Pair<String, ParsedIntentInfo>> list = new ArrayList<>(size);
             for (int i = 0; i < size; ++i) {
                 list.add(Pair.create(source.readString(), source.readParcelable(
-                        ParsedIntentInfoImpl.class.getClassLoader(), android.content.pm.parsing.component.ParsedIntentInfo.class)));
+                        ParsedIntentInfoImpl.class.getClassLoader())));
             }
 
             return list;
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java b/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java
index 45038cf..2145e44 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java
@@ -116,7 +116,7 @@
         this.requestRes = in.readInt();
         this.protectionLevel = in.readInt();
         this.tree = in.readBoolean();
-        this.parsedPermissionGroup = in.readParcelable(boot, android.content.pm.parsing.component.ParsedPermissionGroup.class);
+        this.parsedPermissionGroup = in.readParcelable(boot);
         this.knownCerts = sForStringSet.unparcel(in);
     }
 
diff --git a/core/java/android/debug/AdbManager.java b/core/java/android/debug/AdbManager.java
index 7714dd8..243f801 100644
--- a/core/java/android/debug/AdbManager.java
+++ b/core/java/android/debug/AdbManager.java
@@ -38,6 +38,7 @@
      *
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.MANAGE_DEBUGGING)
     public static final String WIRELESS_DEBUG_STATE_CHANGED_ACTION =
             "com.android.server.adb.WIRELESS_DEBUG_STATUS";
 
@@ -46,6 +47,7 @@
      *
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.MANAGE_DEBUGGING)
     public static final String WIRELESS_DEBUG_PAIRED_DEVICES_ACTION =
             "com.android.server.adb.WIRELESS_DEBUG_PAIRED_DEVICES";
 
@@ -59,6 +61,7 @@
      *
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.MANAGE_DEBUGGING)
     public static final String WIRELESS_DEBUG_PAIRING_RESULT_ACTION =
             "com.android.server.adb.WIRELESS_DEBUG_PAIRING_RESULT";
 
diff --git a/core/java/android/graphics/fonts/FontUpdateRequest.java b/core/java/android/graphics/fonts/FontUpdateRequest.java
index dae09f0..cda1638 100644
--- a/core/java/android/graphics/fonts/FontUpdateRequest.java
+++ b/core/java/android/graphics/fonts/FontUpdateRequest.java
@@ -235,7 +235,7 @@
             public Family createFromParcel(Parcel source) {
                 String familyName = source.readString8();
                 List<Font> fonts = source.readParcelableList(
-                        new ArrayList<>(), Font.class.getClassLoader(), android.graphics.fonts.FontUpdateRequest.Font.class);
+                        new ArrayList<>(), Font.class.getClassLoader());
                 return new Family(familyName, fonts);
             }
 
@@ -379,9 +379,9 @@
 
     protected FontUpdateRequest(Parcel in) {
         mType = in.readInt();
-        mFd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader(), android.os.ParcelFileDescriptor.class);
+        mFd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
         mSignature = in.readBlob();
-        mFontFamily = in.readParcelable(FontConfig.FontFamily.class.getClassLoader(), android.graphics.fonts.FontUpdateRequest.Family.class);
+        mFontFamily = in.readParcelable(FontConfig.FontFamily.class.getClassLoader());
     }
 
     public @Type int getType() {
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index 0c03948..e6b762a 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -65,7 +65,7 @@
         mAuthenticators = in.readInt();
         mDisallowBiometricsIfPolicyExists = in.readBoolean();
         mReceiveSystemEvents = in.readBoolean();
-        mAllowedSensorIds = in.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class);
+        mAllowedSensorIds = in.readArrayList(Integer.class.getClassLoader());
         mAllowBackgroundAuthentication = in.readBoolean();
         mIgnoreEnrollmentState = in.readBoolean();
     }
diff --git a/core/java/android/hardware/biometrics/SensorPropertiesInternal.java b/core/java/android/hardware/biometrics/SensorPropertiesInternal.java
index 1490ea1..f365ee6 100644
--- a/core/java/android/hardware/biometrics/SensorPropertiesInternal.java
+++ b/core/java/android/hardware/biometrics/SensorPropertiesInternal.java
@@ -60,7 +60,7 @@
         sensorStrength = in.readInt();
         maxEnrollmentsPerUser = in.readInt();
         componentInfo = new ArrayList<>();
-        in.readList(componentInfo, ComponentInfoInternal.class.getClassLoader(), android.hardware.biometrics.ComponentInfoInternal.class);
+        in.readList(componentInfo, ComponentInfoInternal.class.getClassLoader());
         resetLockoutRequiresHardwareAuthToken = in.readBoolean();
         resetLockoutRequiresChallenge = in.readBoolean();
     }
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 86ae3a3..5df64e3 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -599,7 +599,8 @@
 
         synchronized (mSurfacesLock) {
             mSurfaceSet.clear();
-            Parcelable[] parcelableArray = in.readParcelableArray(Surface.class.getClassLoader());
+            Parcelable[] parcelableArray = in.readParcelableArray(Surface.class.getClassLoader(),
+                    Surface.class);
             if (parcelableArray != null) {
                 for (Parcelable p : parcelableArray) {
                     Surface s = (Surface) p;
diff --git a/core/java/android/hardware/face/FaceAuthenticationFrame.java b/core/java/android/hardware/face/FaceAuthenticationFrame.java
index a53aad7..f39d634 100644
--- a/core/java/android/hardware/face/FaceAuthenticationFrame.java
+++ b/core/java/android/hardware/face/FaceAuthenticationFrame.java
@@ -46,7 +46,7 @@
     }
 
     private FaceAuthenticationFrame(@NonNull Parcel source) {
-        mData = source.readParcelable(FaceDataFrame.class.getClassLoader(), android.hardware.face.FaceDataFrame.class);
+        mData = source.readParcelable(FaceDataFrame.class.getClassLoader());
     }
 
     @Override
diff --git a/core/java/android/hardware/face/FaceEnrollFrame.java b/core/java/android/hardware/face/FaceEnrollFrame.java
index bbccee2..822a579 100644
--- a/core/java/android/hardware/face/FaceEnrollFrame.java
+++ b/core/java/android/hardware/face/FaceEnrollFrame.java
@@ -73,9 +73,9 @@
     }
 
     private FaceEnrollFrame(@NonNull Parcel source) {
-        mCell = source.readParcelable(FaceEnrollCell.class.getClassLoader(), android.hardware.face.FaceEnrollCell.class);
+        mCell = source.readParcelable(FaceEnrollCell.class.getClassLoader());
         mStage = source.readInt();
-        mData = source.readParcelable(FaceDataFrame.class.getClassLoader(), android.hardware.face.FaceDataFrame.class);
+        mData = source.readParcelable(FaceDataFrame.class.getClassLoader());
     }
 
     @Override
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index b970559..56f8142 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -306,21 +306,22 @@
             throw new IllegalArgumentException("Must supply an enrollment callback");
         }
 
-        if (cancel != null && cancel.isCanceled()) {
-            Slog.w(TAG, "enrollment already canceled");
-            return;
+        if (cancel != null) {
+            if (cancel.isCanceled()) {
+                Slog.w(TAG, "enrollment already canceled");
+                return;
+            } else {
+                cancel.setOnCancelListener(new OnEnrollCancelListener());
+            }
         }
 
         if (mService != null) {
             try {
                 mEnrollmentCallback = callback;
                 Trace.beginSection("FaceManager#enroll");
-                final long enrollId = mService.enroll(userId, mToken, hardwareAuthToken,
-                        mServiceReceiver, mContext.getOpPackageName(), disabledFeatures,
-                        previewSurface, debugConsent);
-                if (cancel != null) {
-                    cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId));
-                }
+                mService.enroll(userId, mToken, hardwareAuthToken, mServiceReceiver,
+                        mContext.getOpPackageName(), disabledFeatures, previewSurface,
+                        debugConsent);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Remote exception in enroll: ", e);
                 // Though this may not be a hardware issue, it will cause apps to give up or
@@ -358,20 +359,21 @@
             throw new IllegalArgumentException("Must supply an enrollment callback");
         }
 
-        if (cancel != null && cancel.isCanceled()) {
-            Slog.w(TAG, "enrollRemotely is already canceled.");
-            return;
+        if (cancel != null) {
+            if (cancel.isCanceled()) {
+                Slog.w(TAG, "enrollRemotely is already canceled.");
+                return;
+            } else {
+                cancel.setOnCancelListener(new OnEnrollCancelListener());
+            }
         }
 
         if (mService != null) {
             try {
                 mEnrollmentCallback = callback;
                 Trace.beginSection("FaceManager#enrollRemotely");
-                final long enrolId = mService.enrollRemotely(userId, mToken, hardwareAuthToken,
-                        mServiceReceiver, mContext.getOpPackageName(), disabledFeatures);
-                if (cancel != null) {
-                    cancel.setOnCancelListener(new OnEnrollCancelListener(enrolId));
-                }
+                mService.enrollRemotely(userId, mToken, hardwareAuthToken, mServiceReceiver,
+                        mContext.getOpPackageName(), disabledFeatures);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Remote exception in enrollRemotely: ", e);
                 // Though this may not be a hardware issue, it will cause apps to give up or
@@ -711,10 +713,10 @@
         }
     }
 
-    private void cancelEnrollment(long requestId) {
+    private void cancelEnrollment() {
         if (mService != null) {
             try {
-                mService.cancelEnrollment(mToken, requestId);
+                mService.cancelEnrollment(mToken);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -1098,16 +1100,9 @@
     }
 
     private class OnEnrollCancelListener implements OnCancelListener {
-        private final long mAuthRequestId;
-
-        private OnEnrollCancelListener(long id) {
-            mAuthRequestId = id;
-        }
-
         @Override
         public void onCancel() {
-            Slog.d(TAG, "Cancel face enrollment requested for: " + mAuthRequestId);
-            cancelEnrollment(mAuthRequestId);
+            cancelEnrollment();
         }
     }
 
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 989b001..e919824 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -76,16 +76,15 @@
     void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId);
 
     // Start face enrollment
-    long enroll(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
-            String opPackageName, in int [] disabledFeatures,
-            in Surface previewSurface, boolean debugConsent);
+    void enroll(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
+            String opPackageName, in int [] disabledFeatures, in Surface previewSurface, boolean debugConsent);
 
     // Start remote face enrollment
-    long enrollRemotely(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
+    void enrollRemotely(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
             String opPackageName, in int [] disabledFeatures);
 
     // Cancel enrollment in progress
-    void cancelEnrollment(IBinder token, long requestId);
+    void cancelEnrollment(IBinder token);
 
     // Removes the specified face enrollment for the specified userId.
     void remove(IBinder token, int faceId, int userId, IFaceServiceReceiver receiver,
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index acf9427..fe04e5d 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -183,16 +183,9 @@
     }
 
     private class OnEnrollCancelListener implements OnCancelListener {
-        private final long mAuthRequestId;
-
-        private OnEnrollCancelListener(long id) {
-            mAuthRequestId = id;
-        }
-
         @Override
         public void onCancel() {
-            Slog.d(TAG, "Cancel fingerprint enrollment requested for: " + mAuthRequestId);
-            cancelEnrollment(mAuthRequestId);
+            cancelEnrollment();
         }
     }
 
@@ -653,19 +646,20 @@
             throw new IllegalArgumentException("Must supply an enrollment callback");
         }
 
-        if (cancel != null && cancel.isCanceled()) {
-            Slog.w(TAG, "enrollment already canceled");
-            return;
+        if (cancel != null) {
+            if (cancel.isCanceled()) {
+                Slog.w(TAG, "enrollment already canceled");
+                return;
+            } else {
+                cancel.setOnCancelListener(new OnEnrollCancelListener());
+            }
         }
 
         if (mService != null) {
             try {
                 mEnrollmentCallback = callback;
-                final long enrollId = mService.enroll(mToken, hardwareAuthToken, userId,
-                        mServiceReceiver, mContext.getOpPackageName(), enrollReason);
-                if (cancel != null) {
-                    cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId));
-                }
+                mService.enroll(mToken, hardwareAuthToken, userId, mServiceReceiver,
+                        mContext.getOpPackageName(), enrollReason);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Remote exception in enroll: ", e);
                 // Though this may not be a hardware issue, it will cause apps to give up or try
@@ -1308,9 +1302,9 @@
         return allSensors.isEmpty() ? null : allSensors.get(0);
     }
 
-    private void cancelEnrollment(long requestId) {
+    private void cancelEnrollment() {
         if (mService != null) try {
-            mService.cancelEnrollment(mToken, requestId);
+            mService.cancelEnrollment(mToken);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index cbff8b1..ba1dc6d 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -84,11 +84,11 @@
     void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId);
 
     // Start fingerprint enrollment
-    long enroll(IBinder token, in byte [] hardwareAuthToken, int userId, IFingerprintServiceReceiver receiver,
+    void enroll(IBinder token, in byte [] hardwareAuthToken, int userId, IFingerprintServiceReceiver receiver,
             String opPackageName, int enrollReason);
 
     // Cancel enrollment in progress
-    void cancelEnrollment(IBinder token, long requestId);
+    void cancelEnrollment(IBinder token);
 
     // Any errors resulting from this call will be returned to the listener
     void remove(IBinder token, int fingerId, int userId, IFingerprintServiceReceiver receiver,
diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java
index 9468ca2..54060cc 100644
--- a/core/java/android/hardware/location/ContextHubClient.java
+++ b/core/java/android/hardware/location/ContextHubClient.java
@@ -15,6 +15,7 @@
  */
 package android.hardware.location;
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
@@ -118,13 +119,14 @@
      * is newly generated (e.g. any regeneration of a callback client, or generation
      * of a non-equal PendingIntent client), the ID will not be the same.
      *
-     * @return The ID of this ContextHubClient.
+     * @return The ID of this ContextHubClient, in the range [0, 65535].
      */
+    @IntRange(from = 0, to = 65535)
     public int getId() {
         if (mId == null) {
             throw new IllegalStateException("ID was not set");
         }
-        return mId;
+        return (0x0000FFFF & mId);
     }
 
     /**
diff --git a/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java b/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java
index 310ebe9..78cca96 100644
--- a/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java
+++ b/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java
@@ -81,7 +81,7 @@
                     int monitoringType = source.readInt();
                     int monitoringStatus = source.readInt();
                     int sourceTechnologies = source.readInt();
-                    Location location = source.readParcelable(classLoader, android.location.Location.class);
+                    Location location = source.readParcelable(classLoader);
 
                     return new GeofenceHardwareMonitorEvent(
                             monitoringType,
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 6ea2ac4..4cc001a 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -436,7 +436,8 @@
             mNumAudioSources = in.readInt();
             mIsInitializationRequired = in.readInt() == 1;
             mIsCaptureSupported = in.readInt() == 1;
-            Parcelable[] tmp = in.readParcelableArray(BandDescriptor.class.getClassLoader());
+            Parcelable[] tmp = in.readParcelableArray(BandDescriptor.class.getClassLoader(),
+                    BandDescriptor.class);
             mBands = new BandDescriptor[tmp.length];
             for (int i = 0; i < tmp.length; i++) {
                 mBands[i] = (BandDescriptor) tmp[i];
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 588f7cd..09d5085 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -54,6 +54,7 @@
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
+import android.annotation.AnyThread;
 import android.annotation.CallSuper;
 import android.annotation.DrawableRes;
 import android.annotation.IntDef;
@@ -83,6 +84,7 @@
 import android.os.IBinder;
 import android.os.ResultReceiver;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.Trace;
 import android.provider.Settings;
 import android.text.InputType;
@@ -305,6 +307,44 @@
     static final boolean DEBUG = false;
 
     /**
+     * Key for a boolean value that tells whether {@link InputMethodService} is responsible for
+     * rendering the back button and the IME switcher button or not when the gestural navigation is
+     * enabled.
+     *
+     * <p>This sysprop is just ignored when the gestural navigation mode is not enabled.</p>
+     *
+     * <p>
+     * To avoid complexity that is not necessary for production, you always need to reboot the
+     * device after modifying this flag as follows:
+     * <pre>
+     * $ adb root
+     * $ adb shell setprop persist.sys.ime.can_render_gestural_nav_buttons true
+     * $ adb reboot
+     * </pre>
+     * </p>
+     */
+    private static final String PROP_CAN_RENDER_GESTURAL_NAV_BUTTONS =
+            "persist.sys.ime.can_render_gestural_nav_buttons";
+
+    /**
+     * Returns whether {@link InputMethodService} is responsible for rendering the back button and
+     * the IME switcher button or not when the gestural navigation is enabled.
+     *
+     * <p>This method is supposed to be used with an assumption that the same value is returned in
+     * other processes. It is developers' responsibility for rebooting the device when the sysprop
+     * is modified.</p>
+     *
+     * @return {@code true} if {@link InputMethodService} is responsible for rendering the back
+     * button and the IME switcher button when the gestural navigation is enabled.
+     *
+     * @hide
+     */
+    @AnyThread
+    public static boolean canImeRenderGesturalNavButtons() {
+        return SystemProperties.getBoolean(PROP_CAN_RENDER_GESTURAL_NAV_BUTTONS, false);
+    }
+
+    /**
      * Allows the system to optimize the back button affordance based on the presence of software
      * keyboard.
      *
@@ -527,6 +567,7 @@
     private Handler mHandler;
     private boolean mImeSurfaceScheduledForRemoval;
     private ImsConfigurationTracker mConfigTracker = new ImsConfigurationTracker();
+    private boolean mDestroyed;
 
     /** Stylus handwriting Ink window.  */
     private InkWindow mInkWindow;
@@ -607,6 +648,11 @@
         public final void initializeInternal(@NonNull IBinder token,
                 IInputMethodPrivilegedOperations privilegedOperations, int configChanges,
                 boolean stylusHwSupported) {
+            if (mDestroyed) {
+                Log.i(TAG, "The InputMethodService has already onDestroyed()."
+                    + "Ignore the initialization.");
+                return;
+            }
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initializeInternal");
             mConfigTracker.onInitialize(configChanges);
             mPrivOps.set(privilegedOperations);
@@ -1492,6 +1538,7 @@
     }
 
     @Override public void onDestroy() {
+        mDestroyed = true;
         super.onDestroy();
         mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
                 mInsetsComputer);
@@ -1868,15 +1915,19 @@
     
     void updateExtractFrameVisibility() {
         final int vis;
+        updateCandidatesVisibility(mCandidatesVisibility == View.VISIBLE);
+
         if (isFullscreenMode()) {
             vis = mExtractViewHidden ? View.INVISIBLE : View.VISIBLE;
             // "vis" should be applied for the extract frame as well in the fullscreen mode.
             mExtractFrame.setVisibility(vis);
         } else {
-            vis = View.VISIBLE;
+            // mFullscreenArea visibility will according the candidate frame visibility once the
+            // extract frame is gone.
+            vis = mCandidatesVisibility;
             mExtractFrame.setVisibility(View.GONE);
         }
-        updateCandidatesVisibility(mCandidatesVisibility == View.VISIBLE);
+
         if (mDecorViewWasVisible && mFullscreenArea.getVisibility() != vis) {
             int animRes = mThemeAttrs.getResourceId(vis == View.VISIBLE
                     ? com.android.internal.R.styleable.InputMethodService_imeExtractEnterAnimation
diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java
index 726d870..5554137 100644
--- a/core/java/android/net/Ikev2VpnProfile.java
+++ b/core/java/android/net/Ikev2VpnProfile.java
@@ -35,6 +35,7 @@
 import android.annotation.RequiresFeature;
 import android.content.pm.PackageManager;
 import android.security.Credentials;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.net.VpnProfile;
@@ -70,6 +71,7 @@
  *     Exchange, Version 2 (IKEv2)</a>
  */
 public final class Ikev2VpnProfile extends PlatformVpnProfile {
+    private static final String TAG = Ikev2VpnProfile.class.getSimpleName();
     /** Prefix for when a Private Key is an alias to look for in KeyStore @hide */
     public static final String PREFIX_KEYSTORE_ALIAS = "KEYSTORE_ALIAS:";
     /** Prefix for when a Private Key is stored directly in the profile @hide */
@@ -524,7 +526,10 @@
                 throw new IllegalArgumentException("Invalid auth method set");
         }
 
-        builder.setExcludeLocalRoutes(profile.excludeLocalRoutes);
+        if (profile.excludeLocalRoutes && !profile.isBypassable) {
+            Log.w(TAG, "ExcludeLocalRoutes should only be set in the bypassable VPN");
+        }
+        builder.setExcludeLocalRoutes(profile.excludeLocalRoutes && profile.isBypassable);
 
         return builder.build();
     }
@@ -911,9 +916,23 @@
         }
 
         /**
-         *  Sets whether the local traffic is exempted from the VPN.
+         * Sets whether the local traffic is exempted from the VPN.
          *
-         *  @hide TODO(184750836): unhide once the implementation is completed
+         * When this is set, the system will not use the VPN network when an app
+         * tries to send traffic for an IP address that is on a local network.
+         *
+         * Note that there are important security implications. In particular, the
+         * networks that the device connects to typically decides what IP addresses
+         * are part of the local network. This means that for VPNs setting this
+         * flag, it is possible for anybody to set up a public network in such a
+         * way that traffic to arbitrary IP addresses will bypass the VPN, including
+         * traffic to services like DNS. When using this API, please consider the
+         * security implications for your particular case.
+         *
+         * Note that because the local traffic will always bypass the VPN,
+         * it is not possible to set this flag on a non-bypassable VPN.
+         *
+         * @hide TODO(184750836): unhide once the implementation is completed
          */
         @NonNull
         @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
diff --git a/core/java/android/net/InterfaceConfiguration.java b/core/java/android/net/InterfaceConfiguration.java
index 1c4089c..37425ff 100644
--- a/core/java/android/net/InterfaceConfiguration.java
+++ b/core/java/android/net/InterfaceConfiguration.java
@@ -160,7 +160,6 @@
         }
     }
 
-    @SuppressWarnings("UnsafeParcelApi")
     public static final @android.annotation.NonNull Creator<InterfaceConfiguration> CREATOR = new Creator<
             InterfaceConfiguration>() {
         public InterfaceConfiguration createFromParcel(Parcel in) {
diff --git a/core/java/android/net/InternalNetworkUpdateRequest.java b/core/java/android/net/InternalNetworkUpdateRequest.java
index 6f09383..f42c4b7 100644
--- a/core/java/android/net/InternalNetworkUpdateRequest.java
+++ b/core/java/android/net/InternalNetworkUpdateRequest.java
@@ -17,7 +17,6 @@
 package android.net;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -27,7 +26,7 @@
 public final class InternalNetworkUpdateRequest implements Parcelable {
     @NonNull
     private final StaticIpConfiguration mIpConfig;
-    @Nullable
+    @NonNull
     private final NetworkCapabilities mNetworkCapabilities;
 
     @NonNull
@@ -37,20 +36,16 @@
 
     @NonNull
     public NetworkCapabilities getNetworkCapabilities() {
-        return mNetworkCapabilities == null
-                ? null : new NetworkCapabilities(mNetworkCapabilities);
+        return new NetworkCapabilities(mNetworkCapabilities);
     }
 
     /** @hide */
     public InternalNetworkUpdateRequest(@NonNull final StaticIpConfiguration ipConfig,
-            @Nullable final NetworkCapabilities networkCapabilities) {
+            @NonNull final NetworkCapabilities networkCapabilities) {
         Objects.requireNonNull(ipConfig);
+        Objects.requireNonNull(networkCapabilities);
         mIpConfig = new StaticIpConfiguration(ipConfig);
-        if (null == networkCapabilities) {
-            mNetworkCapabilities = null;
-        } else {
-            mNetworkCapabilities = new NetworkCapabilities(networkCapabilities);
-        }
+        mNetworkCapabilities = new NetworkCapabilities(networkCapabilities);
     }
 
     private InternalNetworkUpdateRequest(@NonNull final Parcel source) {
diff --git a/core/java/android/net/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java
index 596f431..ab1f542 100644
--- a/core/java/android/net/NetworkPolicy.java
+++ b/core/java/android/net/NetworkPolicy.java
@@ -142,8 +142,8 @@
     }
 
     private NetworkPolicy(Parcel source) {
-        template = source.readParcelable(null, android.net.NetworkTemplate.class);
-        cycleRule = source.readParcelable(null, android.util.RecurrenceRule.class);
+        template = source.readParcelable(null);
+        cycleRule = source.readParcelable(null);
         warningBytes = source.readLong();
         limitBytes = source.readLong();
         lastWarningSnooze = source.readLong();
diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
index 5c28553..3193826 100644
--- a/core/java/android/net/VpnManager.java
+++ b/core/java/android/net/VpnManager.java
@@ -96,6 +96,141 @@
      */
     public static final String NOTIFICATION_CHANNEL_VPN = "VPN";
 
+    /**
+     * Action sent in the intent when an error occurred.
+     *
+     * @hide
+     */
+    public static final String ACTION_VPN_MANAGER_ERROR = "android.net.action.VPN_MANAGER_ERROR";
+
+    /**
+     * An IKE protocol error. Codes are the codes from IkeProtocolException, RFC 7296.
+     *
+     * @hide
+     */
+    public static final String CATEGORY_ERROR_IKE = "android.net.category.ERROR_IKE";
+
+    /**
+     * User deactivated the VPN, either by turning it off or selecting a different VPN provider.
+     * The error code is always 0.
+     *
+     * @hide
+     */
+    public static final String CATEGORY_ERROR_USER_DEACTIVATED =
+            "android.net.category.ERROR_USER_DEACTIVATED";
+
+    /**
+     * Network error. Error codes are ERROR_CODE_NETWORK_*.
+     *
+     * @hide
+     */
+    public static final String CATEGORY_ERROR_NETWORK = "android.net.category.ERROR_NETWORK";
+
+    /**
+     * The key of the session that experienced this error, as returned by
+     * startProvisionedVpnProfileSession.
+     *
+     * @hide
+     */
+    public static final String EXTRA_SESSION_KEY = "android.net.extra.SESSION_KEY";
+
+    /**
+     * Extra for the Network object that was the underlying network at the time of the failure, or
+     * null if none.
+     *
+     * @hide
+     */
+    public static final String EXTRA_UNDERLYING_NETWORK = "android.net.extra.UNDERLYING_NETWORK";
+
+    /**
+     * The NetworkCapabilities of the underlying network.
+     *
+     * @hide
+     */
+    public static final String EXTRA_UNDERLYING_NETWORK_CAPABILITIES =
+            "android.net.extra.UNDERLYING_NETWORK_CAPABILITIES";
+
+    /**
+     * The LinkProperties of the underlying network.
+     *
+     * @hide
+     */
+    public static final String EXTRA_UNDERLYING_LINK_PROPERTIES =
+            "android.net.extra.UNDERLYING_LINK_PROPERTIES";
+
+    /**
+     * A long timestamp with SystemClock.elapsedRealtime base for when the event happened.
+     *
+     * @hide
+     */
+    public static final String EXTRA_TIMESTAMP = "android.net.extra.TIMESTAMP";
+
+    /**
+     * Extra for the error type. This is ERROR_NOT_RECOVERABLE or ERROR_RECOVERABLE.
+     *
+     * @hide
+     */
+    public static final String EXTRA_ERROR_TYPE = "android.net.extra.ERROR_TYPE";
+
+    /**
+     * Extra for the error code. The value will be 0 for CATEGORY_ERROR_USER_DEACTIVATED, one of
+     * ERROR_CODE_NETWORK_* for ERROR_CATEGORY_NETWORK or one of values defined in
+     * IkeProtocolException#ErrorType for CATEGORY_ERROR_IKE.
+     *
+     * @hide
+     */
+    public static final String EXTRA_ERROR_CODE = "android.net.extra.ERROR_CODE";
+
+    /**
+     * This error is fatal, e.g. the VPN was disabled or configuration error. The stack will not
+     * retry connection.
+     *
+     * @hide
+     */
+    public static final int ERROR_NOT_RECOVERABLE = 1;
+
+    /**
+     * The stack experienced an error but will retry with exponential backoff, e.g. network timeout.
+     *
+     * @hide
+     */
+    public static final int ERROR_RECOVERABLE = 2;
+
+    /**
+     * An error code to indicate that there was an UnknownHostException.
+     *
+     * @hide
+     */
+    public static final int ERROR_CODE_NETWORK_UNKNOWN_HOST = 0;
+
+    /**
+     * An error code to indicate that there is a SocketTimeoutException.
+     *
+     * @hide
+     */
+    public static final int ERROR_CODE_NETWORK_TIMEOUT = 1;
+
+    /**
+     * An error code to indicate that the connection is refused.
+     *
+     * @hide
+     */
+    public static final int ERROR_CODE_NETWORK_CONNECT = 2;
+
+    /**
+     * An error code to indicate the connection was reset. (e.g. SocketException)
+     *
+     * @hide
+     */
+    public static final int ERROR_CODE_NETWORK_CONNECTION_RESET = 3;
+
+    /**
+     * An error code to indicate that there is an IOException.
+     *
+     * @hide
+     */
+    public static final int ERROR_CODE_NETWORK_IO = 4;
+
     /** @hide */
     @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM, TYPE_VPN_LEGACY,
             TYPE_VPN_OEM})
diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java
index 2ced056..1ae1b05 100644
--- a/core/java/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -41,6 +41,7 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.net.NetworkUtilsInternal;
 import com.android.internal.net.VpnConfig;
 
@@ -50,6 +51,7 @@
 import java.net.InetAddress;
 import java.net.Socket;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
@@ -471,6 +473,13 @@
         }
     }
 
+    private static void checkNonPrefixBytes(@NonNull InetAddress address, int prefixLength) {
+        final IpPrefix prefix = new IpPrefix(address, prefixLength);
+        if (!prefix.getAddress().equals(address)) {
+            throw new IllegalArgumentException("Bad address");
+        }
+    }
+
     /**
      * Helper class to create a VPN interface. This class should be always
      * used within the scope of the outer {@link VpnService}.
@@ -481,9 +490,9 @@
 
         private final VpnConfig mConfig = new VpnConfig();
         @UnsupportedAppUsage
-        private final List<LinkAddress> mAddresses = new ArrayList<LinkAddress>();
+        private final List<LinkAddress> mAddresses = new ArrayList<>();
         @UnsupportedAppUsage
-        private final List<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
+        private final List<RouteInfo> mRoutes = new ArrayList<>();
 
         public Builder() {
             mConfig.user = VpnService.this.getClass().getName();
@@ -555,7 +564,6 @@
                 throw new IllegalArgumentException("Bad address");
             }
             mAddresses.add(new LinkAddress(address, prefixLength));
-            mConfig.updateAllowedFamilies(address);
             return this;
         }
 
@@ -579,28 +587,68 @@
          * Add a network route to the VPN interface. Both IPv4 and IPv6
          * routes are supported.
          *
+         * If a route with the same destination is already present, its type will be updated.
+         *
+         * @throws IllegalArgumentException if the route is invalid.
+         */
+        @NonNull
+        private Builder addRoute(@NonNull IpPrefix prefix, int type) {
+            check(prefix.getAddress(), prefix.getPrefixLength());
+
+            final RouteInfo newRoute = new RouteInfo(prefix, /* gateway */
+                    null, /* interface */ null, type);
+
+            final int index = findRouteIndexByDestination(newRoute);
+
+            if (index == -1) {
+                mRoutes.add(newRoute);
+            } else {
+                mRoutes.set(index, newRoute);
+            }
+
+            return this;
+        }
+
+        /**
+         * Add a network route to the VPN interface. Both IPv4 and IPv6
+         * routes are supported.
+         *
          * Adding a route implicitly allows traffic from that address family
          * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
          *
+         * Calling this method overrides previous calls to {@link #excludeRoute} for the same
+         * destination.
+         *
+         * If multiple routes match the packet destination, route with the longest prefix takes
+         * precedence.
+         *
          * @throws IllegalArgumentException if the route is invalid.
          */
         @NonNull
         public Builder addRoute(@NonNull InetAddress address, int prefixLength) {
-            check(address, prefixLength);
+            checkNonPrefixBytes(address, prefixLength);
 
-            int offset = prefixLength / 8;
-            byte[] bytes = address.getAddress();
-            if (offset < bytes.length) {
-                for (bytes[offset] <<= prefixLength % 8; offset < bytes.length; ++offset) {
-                    if (bytes[offset] != 0) {
-                        throw new IllegalArgumentException("Bad address");
-                    }
-                }
-            }
-            mRoutes.add(new RouteInfo(new IpPrefix(address, prefixLength), null, null,
-                RouteInfo.RTN_UNICAST));
-            mConfig.updateAllowedFamilies(address);
-            return this;
+            return addRoute(new IpPrefix(address, prefixLength), RouteInfo.RTN_UNICAST);
+        }
+
+        /**
+         * Add a network route to the VPN interface. Both IPv4 and IPv6
+         * routes are supported.
+         *
+         * Adding a route implicitly allows traffic from that address family
+         * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
+         *
+         * Calling this method overrides previous calls to {@link #excludeRoute} for the same
+         * destination.
+         *
+         * If multiple routes match the packet destination, route with the longest prefix takes
+         * precedence.
+         *
+         * @throws IllegalArgumentException if the route is invalid.
+         */
+        @NonNull
+        public Builder addRoute(@NonNull IpPrefix prefix) {
+            return addRoute(prefix, RouteInfo.RTN_UNICAST);
         }
 
         /**
@@ -611,6 +659,12 @@
          * Adding a route implicitly allows traffic from that address family
          * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
          *
+         * Calling this method overrides previous calls to {@link #excludeRoute} for the same
+         * destination.
+         *
+         * If multiple routes match the packet destination, route with the longest prefix takes
+         * precedence.
+         *
          * @throws IllegalArgumentException if the route is invalid.
          * @see #addRoute(InetAddress, int)
          */
@@ -620,6 +674,23 @@
         }
 
         /**
+         * Exclude a network route from the VPN interface. Both IPv4 and IPv6
+         * routes are supported.
+         *
+         * Calling this method overrides previous calls to {@link #addRoute} for the same
+         * destination.
+         *
+         * If multiple routes match the packet destination, route with the longest prefix takes
+         * precedence.
+         *
+         * @throws IllegalArgumentException if the route is invalid.
+         */
+        @NonNull
+        public Builder excludeRoute(@NonNull IpPrefix prefix) {
+            return addRoute(prefix, RouteInfo.RTN_THROW);
+        }
+
+        /**
          * Add a DNS server to the VPN connection. Both IPv4 and IPv6
          * addresses are supported. If none is set, the DNS servers of
          * the default network will be used.
@@ -900,5 +971,23 @@
                 throw new IllegalStateException(e);
             }
         }
+
+        private int findRouteIndexByDestination(RouteInfo route) {
+            for (int i = 0; i < mRoutes.size(); i++) {
+                if (mRoutes.get(i).getDestination().equals(route.getDestination())) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        /**
+         * Method for testing, to observe mRoutes while builder is being used.
+         * @hide
+         */
+        @VisibleForTesting
+        public List<RouteInfo> routes() {
+            return Collections.unmodifiableList(mRoutes);
+        }
     }
 }
diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
index 1ac3f0a..125b573 100644
--- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
@@ -15,6 +15,9 @@
  */
 package android.net.vcn;
 
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.getMatchCriteriaString;
+
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
 import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER;
 import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER;
@@ -23,8 +26,12 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.net.NetworkCapabilities;
+import android.net.vcn.VcnUnderlyingNetworkTemplate.MatchCriteria;
 import android.os.PersistableBundle;
 import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.ArraySet;
 
@@ -37,32 +44,36 @@
 import java.util.Objects;
 import java.util.Set;
 
-// TODO: Add documents
-/** @hide */
+/**
+ * This class represents a configuration for a network template class of underlying cellular
+ * networks.
+ *
+ * <p>See {@link VcnUnderlyingNetworkTemplate}
+ */
 public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetworkTemplate {
     private static final String ALLOWED_NETWORK_PLMN_IDS_KEY = "mAllowedNetworkPlmnIds";
     @NonNull private final Set<String> mAllowedNetworkPlmnIds;
     private static final String ALLOWED_SPECIFIC_CARRIER_IDS_KEY = "mAllowedSpecificCarrierIds";
     @NonNull private final Set<Integer> mAllowedSpecificCarrierIds;
 
-    private static final String ALLOW_ROAMING_KEY = "mAllowRoaming";
-    private final boolean mAllowRoaming;
+    private static final String ROAMING_MATCH_KEY = "mRoamingMatchCriteria";
+    private final int mRoamingMatchCriteria;
 
-    private static final String REQUIRE_OPPORTUNISTIC_KEY = "mRequireOpportunistic";
-    private final boolean mRequireOpportunistic;
+    private static final String OPPORTUNISTIC_MATCH_KEY = "mOpportunisticMatchCriteria";
+    private final int mOpportunisticMatchCriteria;
 
     private VcnCellUnderlyingNetworkTemplate(
             int networkQuality,
-            boolean allowMetered,
+            int meteredMatchCriteria,
             Set<String> allowedNetworkPlmnIds,
             Set<Integer> allowedSpecificCarrierIds,
-            boolean allowRoaming,
-            boolean requireOpportunistic) {
-        super(NETWORK_PRIORITY_TYPE_CELL, networkQuality, allowMetered);
+            int roamingMatchCriteria,
+            int opportunisticMatchCriteria) {
+        super(NETWORK_PRIORITY_TYPE_CELL, networkQuality, meteredMatchCriteria);
         mAllowedNetworkPlmnIds = new ArraySet<>(allowedNetworkPlmnIds);
         mAllowedSpecificCarrierIds = new ArraySet<>(allowedSpecificCarrierIds);
-        mAllowRoaming = allowRoaming;
-        mRequireOpportunistic = requireOpportunistic;
+        mRoamingMatchCriteria = roamingMatchCriteria;
+        mOpportunisticMatchCriteria = opportunisticMatchCriteria;
 
         validate();
     }
@@ -72,15 +83,17 @@
     protected void validate() {
         super.validate();
         validatePlmnIds(mAllowedNetworkPlmnIds);
-        Objects.requireNonNull(mAllowedSpecificCarrierIds, "allowedCarrierIds is null");
+        Objects.requireNonNull(mAllowedSpecificCarrierIds, "matchingCarrierIds is null");
+        validateMatchCriteria(mRoamingMatchCriteria, "mRoamingMatchCriteria");
+        validateMatchCriteria(mOpportunisticMatchCriteria, "mOpportunisticMatchCriteria");
     }
 
-    private static void validatePlmnIds(Set<String> allowedNetworkPlmnIds) {
-        Objects.requireNonNull(allowedNetworkPlmnIds, "allowedNetworkPlmnIds is null");
+    private static void validatePlmnIds(Set<String> matchingOperatorPlmnIds) {
+        Objects.requireNonNull(matchingOperatorPlmnIds, "matchingOperatorPlmnIds is null");
 
         // A valid PLMN is a concatenation of MNC and MCC, and thus consists of 5 or 6 decimal
         // digits.
-        for (String id : allowedNetworkPlmnIds) {
+        for (String id : matchingOperatorPlmnIds) {
             if ((id.length() == 5 || id.length() == 6) && id.matches("[0-9]+")) {
                 continue;
             } else {
@@ -97,7 +110,7 @@
         Objects.requireNonNull(in, "PersistableBundle is null");
 
         final int networkQuality = in.getInt(NETWORK_QUALITY_KEY);
-        final boolean allowMetered = in.getBoolean(ALLOW_METERED_KEY);
+        final int meteredMatchCriteria = in.getInt(METERED_MATCH_KEY);
 
         final PersistableBundle plmnIdsBundle =
                 in.getPersistableBundle(ALLOWED_NETWORK_PLMN_IDS_KEY);
@@ -114,16 +127,16 @@
                         PersistableBundleUtils.toList(
                                 specificCarrierIdsBundle, INTEGER_DESERIALIZER));
 
-        final boolean allowRoaming = in.getBoolean(ALLOW_ROAMING_KEY);
-        final boolean requireOpportunistic = in.getBoolean(REQUIRE_OPPORTUNISTIC_KEY);
+        final int roamingMatchCriteria = in.getInt(ROAMING_MATCH_KEY);
+        final int opportunisticMatchCriteria = in.getInt(OPPORTUNISTIC_MATCH_KEY);
 
         return new VcnCellUnderlyingNetworkTemplate(
                 networkQuality,
-                allowMetered,
+                meteredMatchCriteria,
                 allowedNetworkPlmnIds,
                 allowedSpecificCarrierIds,
-                allowRoaming,
-                requireOpportunistic);
+                roamingMatchCriteria,
+                opportunisticMatchCriteria);
     }
 
     /** @hide */
@@ -143,35 +156,51 @@
                         new ArrayList<>(mAllowedSpecificCarrierIds), INTEGER_SERIALIZER);
         result.putPersistableBundle(ALLOWED_SPECIFIC_CARRIER_IDS_KEY, specificCarrierIdsBundle);
 
-        result.putBoolean(ALLOW_ROAMING_KEY, mAllowRoaming);
-        result.putBoolean(REQUIRE_OPPORTUNISTIC_KEY, mRequireOpportunistic);
+        result.putInt(ROAMING_MATCH_KEY, mRoamingMatchCriteria);
+        result.putInt(OPPORTUNISTIC_MATCH_KEY, mOpportunisticMatchCriteria);
 
         return result;
     }
 
-    /** Retrieve the allowed PLMN IDs, or an empty set if any PLMN ID is acceptable. */
+    /**
+     * Retrieve the matching operator PLMN IDs, or an empty set if any PLMN ID is acceptable.
+     *
+     * @see Builder#setOperatorPlmnIds(Set)
+     */
     @NonNull
-    public Set<String> getAllowedOperatorPlmnIds() {
+    public Set<String> getOperatorPlmnIds() {
         return Collections.unmodifiableSet(mAllowedNetworkPlmnIds);
     }
 
     /**
-     * Retrieve the allowed specific carrier IDs, or an empty set if any specific carrier ID is
-     * acceptable.
+     * Retrieve the matching sim specific carrier IDs, or an empty set if any sim specific carrier
+     * ID is acceptable.
+     *
+     * @see Builder#setSimSpecificCarrierIds(Set)
      */
     @NonNull
-    public Set<Integer> getAllowedSpecificCarrierIds() {
+    public Set<Integer> getSimSpecificCarrierIds() {
         return Collections.unmodifiableSet(mAllowedSpecificCarrierIds);
     }
 
-    /** Return if roaming is allowed. */
-    public boolean allowRoaming() {
-        return mAllowRoaming;
+    /**
+     * Return the matching criteria for roaming networks.
+     *
+     * @see Builder#setRoaming(int)
+     */
+    @MatchCriteria
+    public int getRoaming() {
+        return mRoamingMatchCriteria;
     }
 
-    /** Return if requiring an opportunistic network. */
-    public boolean requireOpportunistic() {
-        return mRequireOpportunistic;
+    /**
+     * Return the matching criteria for opportunistic cellular subscriptions.
+     *
+     * @see Builder#setOpportunistic(int)
+     */
+    @MatchCriteria
+    public int getOpportunistic() {
+        return mOpportunisticMatchCriteria;
     }
 
     @Override
@@ -180,8 +209,8 @@
                 super.hashCode(),
                 mAllowedNetworkPlmnIds,
                 mAllowedSpecificCarrierIds,
-                mAllowRoaming,
-                mRequireOpportunistic);
+                mRoamingMatchCriteria,
+                mOpportunisticMatchCriteria);
     }
 
     @Override
@@ -197,8 +226,8 @@
         final VcnCellUnderlyingNetworkTemplate rhs = (VcnCellUnderlyingNetworkTemplate) other;
         return Objects.equals(mAllowedNetworkPlmnIds, rhs.mAllowedNetworkPlmnIds)
                 && Objects.equals(mAllowedSpecificCarrierIds, rhs.mAllowedSpecificCarrierIds)
-                && mAllowRoaming == rhs.mAllowRoaming
-                && mRequireOpportunistic == rhs.mRequireOpportunistic;
+                && mRoamingMatchCriteria == rhs.mRoamingMatchCriteria
+                && mOpportunisticMatchCriteria == rhs.mOpportunisticMatchCriteria;
     }
 
     /** @hide */
@@ -206,77 +235,137 @@
     void dumpTransportSpecificFields(IndentingPrintWriter pw) {
         pw.println("mAllowedNetworkPlmnIds: " + mAllowedNetworkPlmnIds.toString());
         pw.println("mAllowedSpecificCarrierIds: " + mAllowedSpecificCarrierIds.toString());
-        pw.println("mAllowRoaming: " + mAllowRoaming);
-        pw.println("mRequireOpportunistic: " + mRequireOpportunistic);
+        pw.println("mRoamingMatchCriteria: " + getMatchCriteriaString(mRoamingMatchCriteria));
+        pw.println(
+                "mOpportunisticMatchCriteria: "
+                        + getMatchCriteriaString(mOpportunisticMatchCriteria));
     }
 
-    /** This class is used to incrementally build WifiNetworkPriority objects. */
-    public static final class Builder extends VcnUnderlyingNetworkTemplate.Builder<Builder> {
+    /** This class is used to incrementally build VcnCellUnderlyingNetworkTemplate objects. */
+    public static final class Builder {
+        private int mNetworkQuality = NETWORK_QUALITY_ANY;
+        private int mMeteredMatchCriteria = MATCH_ANY;
+
         @NonNull private final Set<String> mAllowedNetworkPlmnIds = new ArraySet<>();
         @NonNull private final Set<Integer> mAllowedSpecificCarrierIds = new ArraySet<>();
 
-        private boolean mAllowRoaming = false;
-        private boolean mRequireOpportunistic = false;
+        private int mRoamingMatchCriteria = MATCH_ANY;
+        private int mOpportunisticMatchCriteria = MATCH_ANY;
 
         /** Construct a Builder object. */
         public Builder() {}
 
         /**
-         * Set allowed operator PLMN IDs.
+         * Set the required network quality to match this template.
+         *
+         * <p>Network quality is a aggregation of multiple signals that reflect the network link
+         * metrics. For example, the network validation bit (see {@link
+         * NetworkCapabilities#NET_CAPABILITY_VALIDATED}), estimated first hop transport bandwidth
+         * and signal strength.
+         *
+         * @param networkQuality the required network quality. Defaults to NETWORK_QUALITY_ANY
+         * @hide
+         */
+        @NonNull
+        public Builder setNetworkQuality(@NetworkQuality int networkQuality) {
+            validateNetworkQuality(networkQuality);
+
+            mNetworkQuality = networkQuality;
+            return this;
+        }
+
+        /**
+         * Set the matching criteria for metered networks.
+         *
+         * <p>A template where setMetered(MATCH_REQUIRED) will only match metered networks (one
+         * without NET_CAPABILITY_NOT_METERED). A template where setMetered(MATCH_FORBIDDEN) will
+         * only match a network that is not metered (one with NET_CAPABILITY_NOT_METERED).
+         *
+         * @param matchCriteria the matching criteria for metered networks. Defaults to {@link
+         *     #MATCH_ANY}.
+         * @see NetworkCapabilities#NET_CAPABILITY_NOT_METERED
+         */
+        // The matching getter is defined in the super class. Please see {@link
+        // VcnUnderlyingNetworkTemplate#getMetered()}
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public Builder setMetered(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setMetered");
+
+            mMeteredMatchCriteria = matchCriteria;
+            return this;
+        }
+
+        /**
+         * Set operator PLMN IDs with which a network can match this template.
          *
          * <p>This is used to distinguish cases where roaming agreements may dictate a different
          * priority from a partner's networks.
          *
-         * @param allowedNetworkPlmnIds the allowed operator PLMN IDs in String. Defaults to an
-         *     empty set, allowing ANY PLMN ID. A valid PLMN is a concatenation of MNC and MCC, and
-         *     thus consists of 5 or 6 decimal digits. See {@link SubscriptionInfo#getMccString()}
-         *     and {@link SubscriptionInfo#getMncString()}.
+         * @param operatorPlmnIds the matching operator PLMN IDs in String. Network with one of the
+         *     matching PLMN IDs can match this template. If the set is empty, any PLMN ID will
+         *     match. The default is an empty set. A valid PLMN is a concatenation of MNC and MCC,
+         *     and thus consists of 5 or 6 decimal digits.
+         * @see SubscriptionInfo#getMccString()
+         * @see SubscriptionInfo#getMncString()
          */
         @NonNull
-        public Builder setAllowedOperatorPlmnIds(@NonNull Set<String> allowedNetworkPlmnIds) {
-            validatePlmnIds(allowedNetworkPlmnIds);
+        public Builder setOperatorPlmnIds(@NonNull Set<String> operatorPlmnIds) {
+            validatePlmnIds(operatorPlmnIds);
 
             mAllowedNetworkPlmnIds.clear();
-            mAllowedNetworkPlmnIds.addAll(allowedNetworkPlmnIds);
+            mAllowedNetworkPlmnIds.addAll(operatorPlmnIds);
             return this;
         }
 
         /**
-         * Set allowed specific carrier IDs.
+         * Set sim specific carrier IDs with which a network can match this template.
          *
-         * @param allowedSpecificCarrierIds the allowed specific carrier IDs. Defaults to an empty
-         *     set, allowing ANY carrier ID. See {@link TelephonyManager#getSimSpecificCarrierId()}.
+         * @param simSpecificCarrierIds the matching sim specific carrier IDs. Network with one of
+         *     the sim specific carrier IDs can match this template. If the set is empty, any
+         *     carrier ID will match. The default is an empty set.
+         * @see TelephonyManager#getSimSpecificCarrierId()
          */
         @NonNull
-        public Builder setAllowedSpecificCarrierIds(
-                @NonNull Set<Integer> allowedSpecificCarrierIds) {
-            Objects.requireNonNull(allowedSpecificCarrierIds, "allowedCarrierIds is null");
+        public Builder setSimSpecificCarrierIds(@NonNull Set<Integer> simSpecificCarrierIds) {
+            Objects.requireNonNull(simSpecificCarrierIds, "simSpecificCarrierIds is null");
+
             mAllowedSpecificCarrierIds.clear();
-            mAllowedSpecificCarrierIds.addAll(allowedSpecificCarrierIds);
+            mAllowedSpecificCarrierIds.addAll(simSpecificCarrierIds);
             return this;
         }
 
         /**
-         * Set if roaming is allowed.
+         * Set the matching criteria for roaming networks.
          *
-         * @param allowRoaming the flag to indicate if roaming is allowed. Defaults to {@code
-         *     false}.
+         * <p>A template where setRoaming(MATCH_REQUIRED) will only match roaming networks (one
+         * without NET_CAPABILITY_NOT_ROAMING). A template where setRoaming(MATCH_FORBIDDEN) will
+         * only match a network that is not roaming (one with NET_CAPABILITY_NOT_ROAMING).
+         *
+         * @param matchCriteria the matching criteria for roaming networks. Defaults to {@link
+         *     #MATCH_ANY}.
+         * @see NetworkCapabilities#NET_CAPABILITY_NOT_ROAMING
          */
         @NonNull
-        public Builder setAllowRoaming(boolean allowRoaming) {
-            mAllowRoaming = allowRoaming;
+        public Builder setRoaming(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setRoaming");
+
+            mRoamingMatchCriteria = matchCriteria;
             return this;
         }
 
         /**
-         * Set if requiring an opportunistic network.
+         * Set the matching criteria for opportunistic cellular subscriptions.
          *
-         * @param requireOpportunistic the flag to indicate if caller requires an opportunistic
-         *     network. Defaults to {@code false}.
+         * @param matchCriteria the matching criteria for opportunistic cellular subscriptions.
+         *     Defaults to {@link #MATCH_ANY}.
+         * @see SubscriptionManager#setOpportunistic(boolean, int)
          */
         @NonNull
-        public Builder setRequireOpportunistic(boolean requireOpportunistic) {
-            mRequireOpportunistic = requireOpportunistic;
+        public Builder setOpportunistic(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setOpportunistic");
+
+            mOpportunisticMatchCriteria = matchCriteria;
             return this;
         }
 
@@ -285,17 +374,11 @@
         public VcnCellUnderlyingNetworkTemplate build() {
             return new VcnCellUnderlyingNetworkTemplate(
                     mNetworkQuality,
-                    mAllowMetered,
+                    mMeteredMatchCriteria,
                     mAllowedNetworkPlmnIds,
                     mAllowedSpecificCarrierIds,
-                    mAllowRoaming,
-                    mRequireOpportunistic);
-        }
-
-        /** @hide */
-        @Override
-        Builder self() {
-            return this;
+                    mRoamingMatchCriteria,
+                    mOpportunisticMatchCriteria);
         }
     }
 }
diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java
index fd3fe37..caab152 100644
--- a/core/java/android/net/vcn/VcnConfig.java
+++ b/core/java/android/net/vcn/VcnConfig.java
@@ -173,7 +173,7 @@
             new Parcelable.Creator<VcnConfig>() {
                 @NonNull
                 public VcnConfig createFromParcel(Parcel in) {
-                    return new VcnConfig((PersistableBundle) in.readParcelable(null, android.os.PersistableBundle.class));
+                    return new VcnConfig((PersistableBundle) in.readParcelable(null));
                 }
 
                 @NonNull
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index d07c24a..92956e8 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -16,6 +16,7 @@
 package android.net.vcn;
 
 import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
@@ -42,7 +43,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 import java.util.SortedSet;
@@ -162,30 +163,24 @@
 
     /** @hide */
     @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static final LinkedHashSet<VcnUnderlyingNetworkTemplate>
-            DEFAULT_UNDERLYING_NETWORK_PRIORITIES = new LinkedHashSet<>();
+    public static final List<VcnUnderlyingNetworkTemplate> DEFAULT_UNDERLYING_NETWORK_TEMPLATES =
+            new ArrayList<>();
 
     static {
-        DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add(
+        DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add(
                 new VcnCellUnderlyingNetworkTemplate.Builder()
                         .setNetworkQuality(NETWORK_QUALITY_OK)
-                        .setAllowMetered(true /* allowMetered */)
-                        .setAllowRoaming(true /* allowRoaming */)
-                        .setRequireOpportunistic(true /* requireOpportunistic */)
+                        .setOpportunistic(MATCH_REQUIRED)
                         .build());
 
-        DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add(
+        DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add(
                 new VcnWifiUnderlyingNetworkTemplate.Builder()
                         .setNetworkQuality(NETWORK_QUALITY_OK)
-                        .setAllowMetered(true /* allowMetered */)
                         .build());
 
-        DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add(
+        DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add(
                 new VcnCellUnderlyingNetworkTemplate.Builder()
                         .setNetworkQuality(NETWORK_QUALITY_OK)
-                        .setAllowMetered(true /* allowMetered */)
-                        .setAllowRoaming(true /* allowRoaming */)
-                        .setRequireOpportunistic(false /* requireOpportunistic */)
                         .build());
     }
 
@@ -200,9 +195,9 @@
 
     /** @hide */
     @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static final String UNDERLYING_NETWORK_PRIORITIES_KEY = "mUnderlyingNetworkPriorities";
+    public static final String UNDERLYING_NETWORK_TEMPLATES_KEY = "mUnderlyingNetworkTemplates";
 
-    @NonNull private final LinkedHashSet<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkPriorities;
+    @NonNull private final List<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkTemplates;
 
     private static final String MAX_MTU_KEY = "mMaxMtu";
     private final int mMaxMtu;
@@ -215,7 +210,7 @@
             @NonNull String gatewayConnectionName,
             @NonNull IkeTunnelConnectionParams tunnelConnectionParams,
             @NonNull Set<Integer> exposedCapabilities,
-            @NonNull LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities,
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
             @NonNull long[] retryIntervalsMs,
             @IntRange(from = MIN_MTU_V6) int maxMtu) {
         mGatewayConnectionName = gatewayConnectionName;
@@ -224,9 +219,9 @@
         mRetryIntervalsMs = retryIntervalsMs;
         mMaxMtu = maxMtu;
 
-        mUnderlyingNetworkPriorities = new LinkedHashSet<>(underlyingNetworkPriorities);
-        if (mUnderlyingNetworkPriorities.isEmpty()) {
-            mUnderlyingNetworkPriorities.addAll(DEFAULT_UNDERLYING_NETWORK_PRIORITIES);
+        mUnderlyingNetworkTemplates = new ArrayList<>(underlyingNetworkTemplates);
+        if (mUnderlyingNetworkTemplates.isEmpty()) {
+            mUnderlyingNetworkTemplates.addAll(DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
         }
 
         validate();
@@ -250,22 +245,19 @@
         mExposedCapabilities = new TreeSet<>(PersistableBundleUtils.toList(
                 exposedCapsBundle, PersistableBundleUtils.INTEGER_DESERIALIZER));
 
-        final PersistableBundle networkPrioritiesBundle =
-                in.getPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY);
+        final PersistableBundle networkTemplatesBundle =
+                in.getPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY);
 
-        if (networkPrioritiesBundle == null) {
-            // UNDERLYING_NETWORK_PRIORITIES_KEY was added in Android T. Thus
+        if (networkTemplatesBundle == null) {
+            // UNDERLYING_NETWORK_TEMPLATES_KEY was added in Android T. Thus
             // VcnGatewayConnectionConfig created on old platforms will not have this data and will
             // be assigned with the default value
-            mUnderlyingNetworkPriorities =
-                    new LinkedHashSet<>(DEFAULT_UNDERLYING_NETWORK_PRIORITIES);
-
+            mUnderlyingNetworkTemplates = new ArrayList<>(DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
         } else {
-            mUnderlyingNetworkPriorities =
-                    new LinkedHashSet<>(
-                            PersistableBundleUtils.toList(
-                                    networkPrioritiesBundle,
-                                    VcnUnderlyingNetworkTemplate::fromPersistableBundle));
+            mUnderlyingNetworkTemplates =
+                    PersistableBundleUtils.toList(
+                            networkTemplatesBundle,
+                            VcnUnderlyingNetworkTemplate::fromPersistableBundle);
         }
 
         mRetryIntervalsMs = in.getLongArray(RETRY_INTERVAL_MS_KEY);
@@ -285,7 +277,7 @@
             checkValidCapability(cap);
         }
 
-        Objects.requireNonNull(mUnderlyingNetworkPriorities, "underlyingNetworkPriorities is null");
+        validateNetworkTemplateList(mUnderlyingNetworkTemplates);
         Objects.requireNonNull(mRetryIntervalsMs, "retryIntervalsMs was null");
         validateRetryInterval(mRetryIntervalsMs);
 
@@ -314,6 +306,19 @@
         }
     }
 
+    private static void validateNetworkTemplateList(
+            List<VcnUnderlyingNetworkTemplate> networkPriorityRules) {
+        Objects.requireNonNull(networkPriorityRules, "networkPriorityRules is null");
+
+        Set<VcnUnderlyingNetworkTemplate> existingRules = new ArraySet<>();
+        for (VcnUnderlyingNetworkTemplate rule : networkPriorityRules) {
+            Objects.requireNonNull(rule, "Found null value VcnUnderlyingNetworkTemplate");
+            if (!existingRules.add(rule)) {
+                throw new IllegalArgumentException("Found duplicate VcnUnderlyingNetworkTemplate");
+            }
+        }
+    }
+
     /**
      * Returns the configured Gateway Connection name.
      *
@@ -368,15 +373,13 @@
     }
 
     /**
-     * Retrieve the configured VcnUnderlyingNetworkTemplate list, or a default list if it is not
-     * configured.
+     * Retrieve the VcnUnderlyingNetworkTemplate list, or a default list if it is not configured.
      *
-     * @see Builder#setVcnUnderlyingNetworkPriorities(LinkedHashSet<VcnUnderlyingNetworkTemplate>)
-     * @hide
+     * @see Builder#setVcnUnderlyingNetworkPriorities(List)
      */
     @NonNull
-    public LinkedHashSet<VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities() {
-        return new LinkedHashSet<>(mUnderlyingNetworkPriorities);
+    public List<VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities() {
+        return new ArrayList<>(mUnderlyingNetworkTemplates);
     }
 
     /**
@@ -415,15 +418,15 @@
                 PersistableBundleUtils.fromList(
                         new ArrayList<>(mExposedCapabilities),
                         PersistableBundleUtils.INTEGER_SERIALIZER);
-        final PersistableBundle networkPrioritiesBundle =
+        final PersistableBundle networkTemplatesBundle =
                 PersistableBundleUtils.fromList(
-                        new ArrayList<>(mUnderlyingNetworkPriorities),
+                        mUnderlyingNetworkTemplates,
                         VcnUnderlyingNetworkTemplate::toPersistableBundle);
 
         result.putString(GATEWAY_CONNECTION_NAME_KEY, mGatewayConnectionName);
         result.putPersistableBundle(TUNNEL_CONNECTION_PARAMS_KEY, tunnelConnectionParamsBundle);
         result.putPersistableBundle(EXPOSED_CAPABILITIES_KEY, exposedCapsBundle);
-        result.putPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY, networkPrioritiesBundle);
+        result.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, networkTemplatesBundle);
         result.putLongArray(RETRY_INTERVAL_MS_KEY, mRetryIntervalsMs);
         result.putInt(MAX_MTU_KEY, mMaxMtu);
 
@@ -436,7 +439,7 @@
                 mGatewayConnectionName,
                 mTunnelConnectionParams,
                 mExposedCapabilities,
-                mUnderlyingNetworkPriorities,
+                mUnderlyingNetworkTemplates,
                 Arrays.hashCode(mRetryIntervalsMs),
                 mMaxMtu);
     }
@@ -451,7 +454,7 @@
         return mGatewayConnectionName.equals(rhs.mGatewayConnectionName)
                 && mTunnelConnectionParams.equals(rhs.mTunnelConnectionParams)
                 && mExposedCapabilities.equals(rhs.mExposedCapabilities)
-                && mUnderlyingNetworkPriorities.equals(rhs.mUnderlyingNetworkPriorities)
+                && mUnderlyingNetworkTemplates.equals(rhs.mUnderlyingNetworkTemplates)
                 && Arrays.equals(mRetryIntervalsMs, rhs.mRetryIntervalsMs)
                 && mMaxMtu == rhs.mMaxMtu;
     }
@@ -465,8 +468,8 @@
         @NonNull private final Set<Integer> mExposedCapabilities = new ArraySet();
 
         @NonNull
-        private final LinkedHashSet<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkPriorities =
-                new LinkedHashSet<>(DEFAULT_UNDERLYING_NETWORK_PRIORITIES);
+        private final List<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkTemplates =
+                new ArrayList<>(DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
 
         @NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS;
         private int mMaxMtu = DEFAULT_MAX_MTU;
@@ -539,27 +542,37 @@
         }
 
         /**
-         * Set the VcnUnderlyingNetworkTemplate list.
+         * Set the list of templates to match underlying networks against, in high-to-low priority
+         * order.
          *
-         * @param underlyingNetworkPriorities a list of unique VcnUnderlyingNetworkPriorities that
-         *     are ordered from most to least preferred, or an empty list to use the default
-         *     prioritization. The default network prioritization is Opportunistic cellular, Carrier
-         *     WiFi and Macro cellular
-         * @return
+         * <p>To select the VCN underlying network, the VCN connection will go through all the
+         * network candidates and return a network matching the highest priority rule.
+         *
+         * <p>If multiple networks match the same rule, the VCN will prefer an already-selected
+         * network as opposed to a new/unselected network. However, if both are new/unselected
+         * networks, a network will be chosen arbitrarily amongst the networks matching the highest
+         * priority rule.
+         *
+         * <p>If all networks fail to match the rules provided, an underlying network will still be
+         * selected (at random if necessary).
+         *
+         * @param underlyingNetworkTemplates a list of unique VcnUnderlyingNetworkTemplates that are
+         *     ordered from most to least preferred, or an empty list to use the default
+         *     prioritization. The default network prioritization order is Opportunistic cellular,
+         *     Carrier WiFi and then Macro cellular.
+         * @return this {@link Builder} instance, for chaining
          */
-        /** @hide */
         @NonNull
         public Builder setVcnUnderlyingNetworkPriorities(
-                @NonNull LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities) {
-            Objects.requireNonNull(
-                    mUnderlyingNetworkPriorities, "underlyingNetworkPriorities is null");
+                @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates) {
+            validateNetworkTemplateList(underlyingNetworkTemplates);
 
-            mUnderlyingNetworkPriorities.clear();
+            mUnderlyingNetworkTemplates.clear();
 
-            if (underlyingNetworkPriorities.isEmpty()) {
-                mUnderlyingNetworkPriorities.addAll(DEFAULT_UNDERLYING_NETWORK_PRIORITIES);
+            if (underlyingNetworkTemplates.isEmpty()) {
+                mUnderlyingNetworkTemplates.addAll(DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
             } else {
-                mUnderlyingNetworkPriorities.addAll(underlyingNetworkPriorities);
+                mUnderlyingNetworkTemplates.addAll(underlyingNetworkTemplates);
             }
 
             return this;
@@ -629,7 +642,7 @@
                     mGatewayConnectionName,
                     mTunnelConnectionParams,
                     mExposedCapabilities,
-                    mUnderlyingNetworkPriorities,
+                    mUnderlyingNetworkTemplates,
                     mRetryIntervalsMs,
                     mMaxMtu);
         }
diff --git a/core/java/android/net/vcn/VcnNetworkPolicyResult.java b/core/java/android/net/vcn/VcnNetworkPolicyResult.java
index fca084a..14e70cf 100644
--- a/core/java/android/net/vcn/VcnNetworkPolicyResult.java
+++ b/core/java/android/net/vcn/VcnNetworkPolicyResult.java
@@ -114,7 +114,7 @@
     public static final @NonNull Creator<VcnNetworkPolicyResult> CREATOR =
             new Creator<VcnNetworkPolicyResult>() {
                 public VcnNetworkPolicyResult createFromParcel(Parcel in) {
-                    return new VcnNetworkPolicyResult(in.readBoolean(), in.readParcelable(null, android.net.NetworkCapabilities.class));
+                    return new VcnNetworkPolicyResult(in.readBoolean(), in.readParcelable(null));
                 }
 
                 public VcnNetworkPolicyResult[] newArray(int size) {
diff --git a/core/java/android/net/vcn/VcnTransportInfo.java b/core/java/android/net/vcn/VcnTransportInfo.java
index 5c47b28..25a2574 100644
--- a/core/java/android/net/vcn/VcnTransportInfo.java
+++ b/core/java/android/net/vcn/VcnTransportInfo.java
@@ -146,7 +146,7 @@
             new Creator<VcnTransportInfo>() {
                 public VcnTransportInfo createFromParcel(Parcel in) {
                     final int subId = in.readInt();
-                    final WifiInfo wifiInfo = in.readParcelable(null, android.net.wifi.WifiInfo.class);
+                    final WifiInfo wifiInfo = in.readParcelable(null);
 
                     // If all fields are their null values, return null TransportInfo to avoid
                     // leaking information about this being a VCN Network (instead of macro
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java b/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java
index 2b5305d..b0d4f3b 100644
--- a/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java
+++ b/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java
@@ -106,7 +106,7 @@
     public static final @NonNull Creator<VcnUnderlyingNetworkPolicy> CREATOR =
             new Creator<VcnUnderlyingNetworkPolicy>() {
                 public VcnUnderlyingNetworkPolicy createFromParcel(Parcel in) {
-                    return new VcnUnderlyingNetworkPolicy(in.readParcelable(null, android.net.vcn.VcnNetworkPolicyResult.class));
+                    return new VcnUnderlyingNetworkPolicy(in.readParcelable(null));
                 }
 
                 public VcnUnderlyingNetworkPolicy[] newArray(int size) {
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
index d306d5c..60fc936 100644
--- a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
@@ -15,6 +15,8 @@
  */
 package android.net.vcn;
 
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
+
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
 
 import android.annotation.IntDef;
@@ -31,17 +33,24 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
-// TODO: Add documents
-/** @hide */
+/**
+ * This class represents a template containing set of underlying network requirements for doing
+ * route selection.
+ *
+ * <p>Apps provisioning a VCN can configure the underlying network priority for each Gateway
+ * Connection by setting a list (in priority order, most to least preferred) of the appropriate
+ * subclasses in the VcnGatewayConnectionConfig. See {@link
+ * VcnGatewayConnectionConfig.Builder#setVcnUnderlyingNetworkPriorities}
+ */
 public abstract class VcnUnderlyingNetworkTemplate {
     /** @hide */
-    protected static final int NETWORK_PRIORITY_TYPE_WIFI = 1;
+    static final int NETWORK_PRIORITY_TYPE_WIFI = 1;
     /** @hide */
-    protected static final int NETWORK_PRIORITY_TYPE_CELL = 2;
+    static final int NETWORK_PRIORITY_TYPE_CELL = 2;
 
-    /** Denotes that any network quality is acceptable */
+    /** Denotes that any network quality is acceptable. @hide */
     public static final int NETWORK_QUALITY_ANY = 0;
-    /** Denotes that network quality needs to be OK */
+    /** Denotes that network quality needs to be OK. @hide */
     public static final int NETWORK_QUALITY_OK = 100000;
 
     private static final SparseArray<String> NETWORK_QUALITY_TO_STRING_MAP = new SparseArray<>();
@@ -56,34 +65,82 @@
     @IntDef({NETWORK_QUALITY_OK, NETWORK_QUALITY_ANY})
     public @interface NetworkQuality {}
 
+    /**
+     * Used to configure the matching criteria of a network characteristic. This may include network
+     * capabilities, or cellular subscription information. Denotes that networks with or without the
+     * characteristic are both acceptable to match this template.
+     */
+    public static final int MATCH_ANY = 0;
+
+    /**
+     * Used to configure the matching criteria of a network characteristic. This may include network
+     * capabilities, or cellular subscription information. Denotes that a network MUST have the
+     * capability in order to match this template.
+     */
+    public static final int MATCH_REQUIRED = 1;
+
+    /**
+     * Used to configure the matching criteria of a network characteristic. This may include network
+     * capabilities, or cellular subscription information. Denotes that a network MUST NOT have the
+     * capability in order to match this template.
+     */
+    public static final int MATCH_FORBIDDEN = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({MATCH_ANY, MATCH_REQUIRED, MATCH_FORBIDDEN})
+    public @interface MatchCriteria {}
+
+    private static final SparseArray<String> MATCH_CRITERIA_TO_STRING_MAP = new SparseArray<>();
+
+    static {
+        MATCH_CRITERIA_TO_STRING_MAP.put(MATCH_ANY, "MATCH_ANY");
+        MATCH_CRITERIA_TO_STRING_MAP.put(MATCH_REQUIRED, "MATCH_REQUIRED");
+        MATCH_CRITERIA_TO_STRING_MAP.put(MATCH_FORBIDDEN, "MATCH_FORBIDDEN");
+    }
+
     private static final String NETWORK_PRIORITY_TYPE_KEY = "mNetworkPriorityType";
     private final int mNetworkPriorityType;
 
     /** @hide */
-    protected static final String NETWORK_QUALITY_KEY = "mNetworkQuality";
+    static final String NETWORK_QUALITY_KEY = "mNetworkQuality";
+
     private final int mNetworkQuality;
 
     /** @hide */
-    protected static final String ALLOW_METERED_KEY = "mAllowMetered";
-    private final boolean mAllowMetered;
+    static final String METERED_MATCH_KEY = "mMeteredMatchCriteria";
+
+    private final int mMeteredMatchCriteria;
 
     /** @hide */
-    protected VcnUnderlyingNetworkTemplate(
-            int networkPriorityType, int networkQuality, boolean allowMetered) {
+    VcnUnderlyingNetworkTemplate(
+            int networkPriorityType, int networkQuality, int meteredMatchCriteria) {
         mNetworkPriorityType = networkPriorityType;
         mNetworkQuality = networkQuality;
-        mAllowMetered = allowMetered;
+        mMeteredMatchCriteria = meteredMatchCriteria;
     }
 
-    private static void validateNetworkQuality(int networkQuality) {
+    /** @hide */
+    static void validateNetworkQuality(int networkQuality) {
         Preconditions.checkArgument(
                 networkQuality == NETWORK_QUALITY_ANY || networkQuality == NETWORK_QUALITY_OK,
                 "Invalid networkQuality:" + networkQuality);
     }
 
     /** @hide */
+    static void validateMatchCriteria(int meteredMatchCriteria, String matchingCapability) {
+        Preconditions.checkArgument(
+                MATCH_CRITERIA_TO_STRING_MAP.contains(meteredMatchCriteria),
+                "Invalid matching criteria: "
+                        + meteredMatchCriteria
+                        + " for "
+                        + matchingCapability);
+    }
+
+    /** @hide */
     protected void validate() {
         validateNetworkQuality(mNetworkQuality);
+        validateMatchCriteria(mMeteredMatchCriteria, "mMeteredMatchCriteria");
     }
 
     /** @hide */
@@ -112,14 +169,14 @@
 
         result.putInt(NETWORK_PRIORITY_TYPE_KEY, mNetworkPriorityType);
         result.putInt(NETWORK_QUALITY_KEY, mNetworkQuality);
-        result.putBoolean(ALLOW_METERED_KEY, mAllowMetered);
+        result.putInt(METERED_MATCH_KEY, mMeteredMatchCriteria);
 
         return result;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mNetworkPriorityType, mNetworkQuality, mAllowMetered);
+        return Objects.hash(mNetworkPriorityType, mNetworkQuality, mMeteredMatchCriteria);
     }
 
     @Override
@@ -131,7 +188,17 @@
         final VcnUnderlyingNetworkTemplate rhs = (VcnUnderlyingNetworkTemplate) other;
         return mNetworkPriorityType == rhs.mNetworkPriorityType
                 && mNetworkQuality == rhs.mNetworkQuality
-                && mAllowMetered == rhs.mAllowMetered;
+                && mMeteredMatchCriteria == rhs.mMeteredMatchCriteria;
+    }
+
+    /** @hide */
+    static String getNameString(SparseArray<String> toStringMap, int key) {
+        return toStringMap.get(key, "Invalid value " + key);
+    }
+
+    /** @hide */
+    static String getMatchCriteriaString(int meteredMatchCriteria) {
+        return getNameString(MATCH_CRITERIA_TO_STRING_MAP, meteredMatchCriteria);
     }
 
     /** @hide */
@@ -148,65 +215,32 @@
 
         pw.println(
                 "mNetworkQuality: "
-                        + NETWORK_QUALITY_TO_STRING_MAP.get(
-                                mNetworkQuality, "Invalid value " + mNetworkQuality));
-        pw.println("mAllowMetered: " + mAllowMetered);
+                        + getNameString(NETWORK_QUALITY_TO_STRING_MAP, mNetworkQuality));
+        pw.println("mMeteredMatchCriteria: " + getMatchCriteriaString(mMeteredMatchCriteria));
         dumpTransportSpecificFields(pw);
 
         pw.decreaseIndent();
     }
 
-    /** Retrieve the required network quality. */
+    /**
+     * Retrieve the required network quality to match this template.
+     *
+     * @see Builder#setNetworkQuality(int)
+     * @hide
+     */
     @NetworkQuality
     public int getNetworkQuality() {
         return mNetworkQuality;
     }
 
-    /** Return if a metered network is allowed. */
-    public boolean allowMetered() {
-        return mAllowMetered;
-    }
-
     /**
-     * This class is used to incrementally build VcnUnderlyingNetworkTemplate objects.
+     * Return the matching criteria for metered networks.
      *
-     * @param <T> The subclass to be built.
+     * @see VcnWifiUnderlyingNetworkTemplate.Builder#setMetered(int)
+     * @see VcnCellUnderlyingNetworkTemplate.Builder#setMetered(int)
      */
-    public abstract static class Builder<T extends Builder<T>> {
-        /** @hide */
-        protected int mNetworkQuality = NETWORK_QUALITY_ANY;
-        /** @hide */
-        protected boolean mAllowMetered = false;
-
-        /** @hide */
-        protected Builder() {}
-
-        /**
-         * Set the required network quality.
-         *
-         * @param networkQuality the required network quality. Defaults to NETWORK_QUALITY_ANY
-         */
-        @NonNull
-        public T setNetworkQuality(@NetworkQuality int networkQuality) {
-            validateNetworkQuality(networkQuality);
-
-            mNetworkQuality = networkQuality;
-            return self();
-        }
-
-        /**
-         * Set if a metered network is allowed.
-         *
-         * @param allowMetered the flag to indicate if a metered network is allowed, defaults to
-         *     {@code false}
-         */
-        @NonNull
-        public T setAllowMetered(boolean allowMetered) {
-            mAllowMetered = allowMetered;
-            return self();
-        }
-
-        /** @hide */
-        abstract T self();
+    @MatchCriteria
+    public int getMetered() {
+        return mMeteredMatchCriteria;
     }
 }
diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
index 6bbb2bf..272ca9d 100644
--- a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
+++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
@@ -16,31 +16,59 @@
 package android.net.vcn;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
+import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER;
+import static com.android.server.vcn.util.PersistableBundleUtils.STRING_SERIALIZER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.net.NetworkCapabilities;
 import android.os.PersistableBundle;
+import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.vcn.util.PersistableBundleUtils;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Objects;
+import java.util.Set;
 
-// TODO: Add documents
-/** @hide */
+/**
+ * This class represents a configuration for a network template class of underlying Carrier WiFi
+ * networks.
+ *
+ * <p>See {@link VcnUnderlyingNetworkTemplate}
+ */
 public final class VcnWifiUnderlyingNetworkTemplate extends VcnUnderlyingNetworkTemplate {
-    private static final String SSID_KEY = "mSsid";
-    @Nullable private final String mSsid;
+    private static final String SSIDS_KEY = "mSsids";
+    @Nullable private final Set<String> mSsids;
 
     private VcnWifiUnderlyingNetworkTemplate(
-            int networkQuality, boolean allowMetered, String ssid) {
-        super(NETWORK_PRIORITY_TYPE_WIFI, networkQuality, allowMetered);
-        mSsid = ssid;
+            int networkQuality, int meteredMatchCriteria, Set<String> ssids) {
+        super(NETWORK_PRIORITY_TYPE_WIFI, networkQuality, meteredMatchCriteria);
+        mSsids = new ArraySet<>(ssids);
 
         validate();
     }
 
     /** @hide */
+    @Override
+    protected void validate() {
+        super.validate();
+        validateSsids(mSsids);
+    }
+
+    private static void validateSsids(Set<String> ssids) {
+        Objects.requireNonNull(ssids, "ssids is null");
+
+        for (String ssid : ssids) {
+            Objects.requireNonNull(ssid, "found null value ssid");
+        }
+    }
+
+    /** @hide */
     @NonNull
     @VisibleForTesting(visibility = Visibility.PROTECTED)
     public static VcnWifiUnderlyingNetworkTemplate fromPersistableBundle(
@@ -48,9 +76,14 @@
         Objects.requireNonNull(in, "PersistableBundle is null");
 
         final int networkQuality = in.getInt(NETWORK_QUALITY_KEY);
-        final boolean allowMetered = in.getBoolean(ALLOW_METERED_KEY);
-        final String ssid = in.getString(SSID_KEY);
-        return new VcnWifiUnderlyingNetworkTemplate(networkQuality, allowMetered, ssid);
+        final int meteredMatchCriteria = in.getInt(METERED_MATCH_KEY);
+
+        final PersistableBundle ssidsBundle = in.getPersistableBundle(SSIDS_KEY);
+        Objects.requireNonNull(ssidsBundle, "ssidsBundle is null");
+        final Set<String> ssids =
+                new ArraySet<String>(
+                        PersistableBundleUtils.toList(ssidsBundle, STRING_DESERIALIZER));
+        return new VcnWifiUnderlyingNetworkTemplate(networkQuality, meteredMatchCriteria, ssids);
     }
 
     /** @hide */
@@ -59,13 +92,17 @@
     @VisibleForTesting(visibility = Visibility.PROTECTED)
     public PersistableBundle toPersistableBundle() {
         final PersistableBundle result = super.toPersistableBundle();
-        result.putString(SSID_KEY, mSsid);
+
+        final PersistableBundle ssidsBundle =
+                PersistableBundleUtils.fromList(new ArrayList<>(mSsids), STRING_SERIALIZER);
+        result.putPersistableBundle(SSIDS_KEY, ssidsBundle);
+
         return result;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(super.hashCode(), mSsid);
+        return Objects.hash(super.hashCode(), mSsids);
     }
 
     @Override
@@ -79,49 +116,95 @@
         }
 
         final VcnWifiUnderlyingNetworkTemplate rhs = (VcnWifiUnderlyingNetworkTemplate) other;
-        return mSsid.equals(rhs.mSsid);
+        return mSsids.equals(rhs.mSsids);
     }
 
     /** @hide */
     @Override
     void dumpTransportSpecificFields(IndentingPrintWriter pw) {
-        pw.println("mSsid: " + mSsid);
+        pw.println("mSsids: " + mSsids);
     }
 
-    /** Retrieve the required SSID, or {@code null} if there is no requirement on SSID. */
-    @Nullable
-    public String getSsid() {
-        return mSsid;
+    /**
+     * Retrieve the matching SSIDs, or an empty set if any SSID is acceptable.
+     *
+     * @see Builder#setSsids(Set)
+     */
+    @NonNull
+    public Set<String> getSsids() {
+        return Collections.unmodifiableSet(mSsids);
     }
 
     /** This class is used to incrementally build VcnWifiUnderlyingNetworkTemplate objects. */
-    public static class Builder extends VcnUnderlyingNetworkTemplate.Builder<Builder> {
-        @Nullable private String mSsid;
+    public static final class Builder {
+        private int mNetworkQuality = NETWORK_QUALITY_ANY;
+        private int mMeteredMatchCriteria = MATCH_ANY;
+        @NonNull private final Set<String> mSsids = new ArraySet<>();
 
         /** Construct a Builder object. */
         public Builder() {}
 
         /**
-         * Set the required SSID.
+         * Set the required network quality to match this template.
          *
-         * @param ssid the required SSID, or {@code null} if any SSID is acceptable.
+         * <p>Network quality is a aggregation of multiple signals that reflect the network link
+         * metrics. For example, the network validation bit (see {@link
+         * NetworkCapabilities#NET_CAPABILITY_VALIDATED}), estimated first hop transport bandwidth
+         * and signal strength.
+         *
+         * @param networkQuality the required network quality. Defaults to NETWORK_QUALITY_ANY
+         * @hide
          */
         @NonNull
-        public Builder setSsid(@Nullable String ssid) {
-            mSsid = ssid;
+        public Builder setNetworkQuality(@NetworkQuality int networkQuality) {
+            validateNetworkQuality(networkQuality);
+
+            mNetworkQuality = networkQuality;
+            return this;
+        }
+
+        /**
+         * Set the matching criteria for metered networks.
+         *
+         * <p>A template where setMetered(MATCH_REQUIRED) will only match metered networks (one
+         * without NET_CAPABILITY_NOT_METERED). A template where setMetered(MATCH_FORBIDDEN) will
+         * only match a network that is not metered (one with NET_CAPABILITY_NOT_METERED).
+         *
+         * @param matchCriteria the matching criteria for metered networks. Defaults to {@link
+         *     #MATCH_ANY}.
+         * @see NetworkCapabilities#NET_CAPABILITY_NOT_METERED
+         */
+        // The matching getter is defined in the super class. Please see {@link
+        // VcnUnderlyingNetworkTemplate#getMetered()}
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public Builder setMetered(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setMetered");
+
+            mMeteredMatchCriteria = matchCriteria;
+            return this;
+        }
+
+        /**
+         * Set the SSIDs with which a network can match this priority rule.
+         *
+         * @param ssids the matching SSIDs. Network with one of the matching SSIDs can match this
+         *     priority rule. If the set is empty, any SSID will match. The default is an empty set.
+         */
+        @NonNull
+        public Builder setSsids(@NonNull Set<String> ssids) {
+            validateSsids(ssids);
+
+            mSsids.clear();
+            mSsids.addAll(ssids);
             return this;
         }
 
         /** Build the VcnWifiUnderlyingNetworkTemplate. */
         @NonNull
         public VcnWifiUnderlyingNetworkTemplate build() {
-            return new VcnWifiUnderlyingNetworkTemplate(mNetworkQuality, mAllowMetered, mSsid);
-        }
-
-        /** @hide */
-        @Override
-        Builder self() {
-            return this;
+            return new VcnWifiUnderlyingNetworkTemplate(
+                    mNetworkQuality, mMeteredMatchCriteria, mSsids);
         }
     }
 }
diff --git a/core/java/android/nfc/BeamShareData.java b/core/java/android/nfc/BeamShareData.java
index 6a40f98..ed3b74a 100644
--- a/core/java/android/nfc/BeamShareData.java
+++ b/core/java/android/nfc/BeamShareData.java
@@ -47,13 +47,13 @@
         @Override
         public BeamShareData createFromParcel(Parcel source) {
             Uri[] uris = null;
-            NdefMessage msg = source.readParcelable(NdefMessage.class.getClassLoader(), android.nfc.NdefMessage.class);
+            NdefMessage msg = source.readParcelable(NdefMessage.class.getClassLoader());
             int numUris = source.readInt();
             if (numUris > 0) {
                 uris = new Uri[numUris];
                 source.readTypedArray(uris, Uri.CREATOR);
             }
-            UserHandle userHandle = source.readParcelable(UserHandle.class.getClassLoader(), android.os.UserHandle.class);
+            UserHandle userHandle = source.readParcelable(UserHandle.class.getClassLoader());
             int flags = source.readInt();
 
             return new BeamShareData(msg, uris, userHandle, flags);
diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java
index 72fb4ae..c62df40 100644
--- a/core/java/android/os/Message.java
+++ b/core/java/android/os/Message.java
@@ -654,7 +654,7 @@
         arg1 = source.readInt();
         arg2 = source.readInt();
         if (source.readInt() != 0) {
-            obj = source.readParcelable(getClass().getClassLoader(), java.lang.Object.class);
+            obj = source.readParcelable(getClass().getClassLoader());
         }
         when = source.readLong();
         data = source.readBundle();
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 92d652d..d4a338b 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -335,6 +335,12 @@
     public static final int USER_ACTIVITY_EVENT_FACE_DOWN = 5;
 
     /**
+     * User activity event type: There is a change in the device state.
+     * @hide
+     */
+    public static final int USER_ACTIVITY_EVENT_DEVICE_STATE = 6;
+
+    /**
      * User activity flag: If already dimmed, extend the dim timeout
      * but do not brighten.  This flag is useful for keeping the screen on
      * a little longer without causing a visible change such as when
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 70aaa5e..ebbfe47 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -2993,7 +2993,7 @@
          *     should be removed.
          */
         public ViolationInfo(Parcel in, boolean unsetGatheringBit) {
-            mViolation = (Violation) in.readSerializable(android.os.strictmode.Violation.class.getClassLoader(), android.os.strictmode.Violation.class);
+            mViolation = (Violation) in.readSerializable();
             int binderStackSize = in.readInt();
             for (int i = 0; i < binderStackSize; i++) {
                 StackTraceElement[] traceElements = new StackTraceElement[in.readInt()];
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 5831573..8834725 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -32,32 +32,30 @@
 public final class VibrationAttributes implements Parcelable {
     private static final String TAG = "VibrationAttributes";
 
-    /**
-     * @hide
-     */
+    /** @hide */
     @IntDef(prefix = { "USAGE_CLASS_" }, value = {
             USAGE_CLASS_UNKNOWN,
             USAGE_CLASS_ALARM,
             USAGE_CLASS_FEEDBACK,
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface UsageClass{}
+    public @interface UsageClass {}
 
-    /**
-     * @hide
-     */
+    /** @hide */
     @IntDef(prefix = { "USAGE_" }, value = {
             USAGE_UNKNOWN,
+            USAGE_ACCESSIBILITY,
             USAGE_ALARM,
-            USAGE_RINGTONE,
-            USAGE_NOTIFICATION,
             USAGE_COMMUNICATION_REQUEST,
-            USAGE_TOUCH,
-            USAGE_PHYSICAL_EMULATION,
             USAGE_HARDWARE_FEEDBACK,
+            USAGE_MEDIA,
+            USAGE_NOTIFICATION,
+            USAGE_PHYSICAL_EMULATION,
+            USAGE_RINGTONE,
+            USAGE_TOUCH,
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface Usage{}
+    public @interface Usage {}
 
     /**
      * Vibration usage filter value to match all usages.
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index ae37a71..5de4556 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -576,7 +576,7 @@
         private final int mRepeatIndex;
 
         Composed(@NonNull Parcel in) {
-            this(in.readArrayList(VibrationEffectSegment.class.getClassLoader(), android.os.vibrator.VibrationEffectSegment.class), in.readInt());
+            this(in.readArrayList(VibrationEffectSegment.class.getClassLoader()), in.readInt());
         }
 
         Composed(@NonNull VibrationEffectSegment segment) {
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index d71ead7..eba9ff1 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -23,11 +23,14 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.content.res.Resources;
 import android.hardware.vibrator.IVibrator;
 import android.media.AudioAttributes;
+import android.os.vibrator.VibrationConfig;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
@@ -49,6 +52,7 @@
      *
      * @hide
      */
+    @TestApi
     public static final int VIBRATION_INTENSITY_OFF = 0;
 
     /**
@@ -56,6 +60,7 @@
      *
      * @hide
      */
+    @TestApi
     public static final int VIBRATION_INTENSITY_LOW = 1;
 
     /**
@@ -63,6 +68,7 @@
      *
      * @hide
      */
+    @TestApi
     public static final int VIBRATION_INTENSITY_MEDIUM = 2;
 
     /**
@@ -70,6 +76,7 @@
      *
      * @hide
      */
+    @TestApi
     public static final int VIBRATION_INTENSITY_HIGH = 3;
 
     /**
@@ -115,16 +122,12 @@
     }
 
     private final String mPackageName;
-    // The default vibration intensity level for haptic feedback.
-    @VibrationIntensity
-    private int mDefaultHapticFeedbackIntensity;
-    // The default vibration intensity level for notifications.
-    @VibrationIntensity
-    private int mDefaultNotificationVibrationIntensity;
-    // The default vibration intensity level for ringtones.
-    @VibrationIntensity
-    private int mDefaultRingVibrationIntensity;
-    private float mHapticChannelMaxVibrationAmplitude;
+    @Nullable
+    private final Resources mResources;
+
+    // This is lazily loaded only for the few clients that need this (e. Settings app).
+    @Nullable
+    private volatile VibrationConfig mVibrationConfig;
 
     /**
      * @hide to prevent subclassing from outside of the framework
@@ -132,8 +135,7 @@
     @UnsupportedAppUsage
     public Vibrator() {
         mPackageName = ActivityThread.currentPackageName();
-        final Context ctx = ActivityThread.currentActivityThread().getSystemContext();
-        loadVibrationConfig(ctx);
+        mResources = null;
     }
 
     /**
@@ -141,26 +143,7 @@
      */
     protected Vibrator(Context context) {
         mPackageName = context.getOpPackageName();
-        loadVibrationConfig(context);
-    }
-
-    private void loadVibrationConfig(Context context) {
-        mDefaultHapticFeedbackIntensity = loadDefaultIntensity(context,
-                com.android.internal.R.integer.config_defaultHapticFeedbackIntensity);
-        mDefaultNotificationVibrationIntensity = loadDefaultIntensity(context,
-                com.android.internal.R.integer.config_defaultNotificationVibrationIntensity);
-        mDefaultRingVibrationIntensity = loadDefaultIntensity(context,
-                com.android.internal.R.integer.config_defaultRingVibrationIntensity);
-        mHapticChannelMaxVibrationAmplitude = loadFloat(context,
-                com.android.internal.R.dimen.config_hapticChannelMaxVibrationAmplitude, 0);
-    }
-
-    private int loadDefaultIntensity(Context ctx, int resId) {
-        return ctx != null ? ctx.getResources().getInteger(resId) : VIBRATION_INTENSITY_MEDIUM;
-    }
-
-    private float loadFloat(Context ctx, int resId, float defaultValue) {
-        return ctx != null ? ctx.getResources().getFloat(resId) : defaultValue;
+        mResources = context.getResources();
     }
 
     /**
@@ -172,31 +155,30 @@
         return VibratorInfo.EMPTY_VIBRATOR_INFO;
     }
 
-    /**
-     * Get the default vibration intensity for haptic feedback.
-     *
-     * @hide
-     */
-    public int getDefaultHapticFeedbackIntensity() {
-        return mDefaultHapticFeedbackIntensity;
+    /** Get the static vibrator configuration from config.xml. */
+    private VibrationConfig getConfig() {
+        if (mVibrationConfig == null) {
+            Resources resources = mResources;
+            if (resources == null) {
+                final Context ctx = ActivityThread.currentActivityThread().getSystemContext();
+                resources = ctx != null ? ctx.getResources() : null;
+            }
+            // This might be constructed more than once, but it only loads static config data from a
+            // xml file, so it would be ok.
+            mVibrationConfig = new VibrationConfig(resources);
+        }
+        return mVibrationConfig;
     }
 
     /**
-     * Get the default vibration intensity for notifications.
+     * Get the default vibration intensity for given usage.
      *
      * @hide
      */
-    public int getDefaultNotificationVibrationIntensity() {
-        return mDefaultNotificationVibrationIntensity;
-    }
-
-    /**
-     * Get the default vibration intensity for ringtones.
-     *
-     * @hide
-     */
-    public int getDefaultRingVibrationIntensity() {
-        return mDefaultRingVibrationIntensity;
+    @TestApi
+    @VibrationIntensity
+    public int getDefaultVibrationIntensity(@VibrationAttributes.Usage int usage) {
+        return getConfig().getDefaultVibrationIntensity(usage);
     }
 
     /**
@@ -280,10 +262,7 @@
      * @hide
      */
     public float getHapticChannelMaximumAmplitude() {
-        if (mHapticChannelMaxVibrationAmplitude <= 0) {
-            return Float.NaN;
-        }
-        return mHapticChannelMaxVibrationAmplitude;
+        return getConfig().getHapticChannelMaximumAmplitude();
     }
 
     /**
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 5271c4d..189e454 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -69,7 +69,7 @@
         mPwlePrimitiveDurationMax = in.readInt();
         mPwleSizeMax = in.readInt();
         mQFactor = in.readFloat();
-        mFrequencyMapping = in.readParcelable(VibratorInfo.class.getClassLoader(), android.os.VibratorInfo.FrequencyMapping.class);
+        mFrequencyMapping = in.readParcelable(VibratorInfo.class.getClassLoader());
     }
 
     /**
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index e899f77..6588b57 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -130,7 +130,7 @@
         int numChains = in.readInt();
         if (numChains > 0) {
             mChains = new ArrayList<>(numChains);
-            in.readParcelableList(mChains, WorkChain.class.getClassLoader(), android.os.WorkSource.WorkChain.class);
+            in.readParcelableList(mChains, WorkChain.class.getClassLoader());
         } else {
             mChains = null;
         }
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 8ee52c2..b78bb25 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -168,7 +168,7 @@
         mExternallyManaged = in.readInt() != 0;
         mAllowMassStorage = in.readInt() != 0;
         mMaxFileSize = in.readLong();
-        mOwner = in.readParcelable(null, android.os.UserHandle.class);
+        mOwner = in.readParcelable(null);
         if (in.readInt() != 0) {
             mUuid = StorageManager.convert(in.readString8());
         } else {
diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java
new file mode 100644
index 0000000..4a61472
--- /dev/null
+++ b/core/java/android/os/vibrator/VibrationConfig.java
@@ -0,0 +1,175 @@
+/*
+ * 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.vibrator;
+
+import static android.os.VibrationAttributes.USAGE_ACCESSIBILITY;
+import static android.os.VibrationAttributes.USAGE_ALARM;
+import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
+import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
+import static android.os.VibrationAttributes.USAGE_MEDIA;
+import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
+import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION;
+import static android.os.VibrationAttributes.USAGE_RINGTONE;
+import static android.os.VibrationAttributes.USAGE_TOUCH;
+import static android.os.VibrationAttributes.USAGE_UNKNOWN;
+
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.os.VibrationAttributes;
+import android.os.Vibrator;
+import android.os.Vibrator.VibrationIntensity;
+
+/**
+ * List of device-specific internal vibration configuration loaded from platform config.xml.
+ *
+ * <p>This should not be public, but some individual values are exposed by {@link Vibrator} by
+ * hidden methods, made available to Settings, SysUI and other platform client code. They can also
+ * be individually exposed with the necessary permissions by the {@link Vibrator} service.
+ *
+ * @hide
+ */
+public class VibrationConfig {
+
+    // TODO(b/191150049): move these to vibrator static config file
+    private final float mHapticChannelMaxVibrationAmplitude;
+    private final int mRampStepDurationMs;
+    private final int mRampDownDurationMs;
+
+    @VibrationIntensity
+    private final int mDefaultAlarmVibrationIntensity;
+    @VibrationIntensity
+    private final int mDefaultHapticFeedbackIntensity;
+    @VibrationIntensity
+    private final int mDefaultMediaVibrationIntensity;
+    @VibrationIntensity
+    private final int mDefaultNotificationVibrationIntensity;
+    @VibrationIntensity
+    private final int mDefaultRingVibrationIntensity;
+
+    /** @hide */
+    public VibrationConfig(@Nullable Resources resources) {
+        mHapticChannelMaxVibrationAmplitude = loadFloat(resources,
+                com.android.internal.R.dimen.config_hapticChannelMaxVibrationAmplitude, 0);
+        mRampDownDurationMs = loadInteger(resources,
+                com.android.internal.R.integer.config_vibrationWaveformRampDownDuration, 0);
+        mRampStepDurationMs = loadInteger(resources,
+                com.android.internal.R.integer.config_vibrationWaveformRampStepDuration, 0);
+
+        mDefaultAlarmVibrationIntensity = loadDefaultIntensity(resources,
+                com.android.internal.R.integer.config_defaultAlarmVibrationIntensity);
+        mDefaultHapticFeedbackIntensity = loadDefaultIntensity(resources,
+                com.android.internal.R.integer.config_defaultHapticFeedbackIntensity);
+        mDefaultMediaVibrationIntensity = loadDefaultIntensity(resources,
+                com.android.internal.R.integer.config_defaultMediaVibrationIntensity);
+        mDefaultNotificationVibrationIntensity = loadDefaultIntensity(resources,
+                com.android.internal.R.integer.config_defaultNotificationVibrationIntensity);
+        mDefaultRingVibrationIntensity = loadDefaultIntensity(resources,
+                com.android.internal.R.integer.config_defaultRingVibrationIntensity);
+    }
+
+    @VibrationIntensity
+    private static int loadDefaultIntensity(@Nullable Resources res, int resId) {
+        int defaultIntensity = Vibrator.VIBRATION_INTENSITY_MEDIUM;
+        int value = loadInteger(res, resId, defaultIntensity);
+        if (value < Vibrator.VIBRATION_INTENSITY_OFF || value > Vibrator.VIBRATION_INTENSITY_HIGH) {
+            return defaultIntensity;
+        }
+        return value;
+    }
+
+    private static float loadFloat(@Nullable Resources res, int resId, float defaultValue) {
+        return res != null ? res.getFloat(resId) : defaultValue;
+    }
+
+    private static int loadInteger(@Nullable Resources res, int resId, int defaultValue) {
+        return res != null ? res.getInteger(resId) : defaultValue;
+    }
+
+    /**
+     * Return the maximum amplitude the vibrator can play using the audio haptic channels.
+     *
+     * @return a positive value representing the maximum absolute value the device can play signals
+     * from audio haptic channels, or {@link Float#NaN NaN} if it's unknown.
+     */
+    public float getHapticChannelMaximumAmplitude() {
+        if (mHapticChannelMaxVibrationAmplitude <= 0) {
+            return Float.NaN;
+        }
+        return mHapticChannelMaxVibrationAmplitude;
+    }
+
+    /**
+     * The duration, in milliseconds, that should be applied to the ramp to turn off the vibrator
+     * when a vibration is cancelled or finished at non-zero amplitude.
+     */
+    public int getRampDownDurationMs() {
+        if (mRampDownDurationMs < 0) {
+            return 0;
+        }
+        return mRampDownDurationMs;
+    }
+
+    /**
+     * The duration, in milliseconds, that should be applied to convert vibration effect's
+     * {@link android.os.vibrator.RampSegment} to a {@link android.os.vibrator.StepSegment} on
+     * devices without PWLE support.
+     */
+    public int getRampStepDurationMs() {
+        if (mRampStepDurationMs < 0) {
+            return 0;
+        }
+        return mRampStepDurationMs;
+    }
+
+    /** Get the default vibration intensity for given usage. */
+    @VibrationIntensity
+    public int getDefaultVibrationIntensity(@VibrationAttributes.Usage int usage) {
+        switch (usage) {
+            case USAGE_ALARM:
+                return mDefaultAlarmVibrationIntensity;
+            case USAGE_NOTIFICATION:
+            case USAGE_COMMUNICATION_REQUEST:
+                return mDefaultNotificationVibrationIntensity;
+            case USAGE_RINGTONE:
+                return mDefaultRingVibrationIntensity;
+            case USAGE_TOUCH:
+            case USAGE_HARDWARE_FEEDBACK:
+            case USAGE_PHYSICAL_EMULATION:
+            case USAGE_ACCESSIBILITY:
+                return mDefaultHapticFeedbackIntensity;
+            case USAGE_MEDIA:
+            case USAGE_UNKNOWN:
+                // fall through
+            default:
+                return mDefaultMediaVibrationIntensity;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "VibrationConfig{"
+                + "mHapticChannelMaxVibrationAmplitude=" + mHapticChannelMaxVibrationAmplitude
+                + ", mRampStepDurationMs=" + mRampStepDurationMs
+                + ", mRampDownDurationMs=" + mRampDownDurationMs
+                + ", mDefaultAlarmIntensity=" + mDefaultAlarmVibrationIntensity
+                + ", mDefaultHapticFeedbackIntensity=" + mDefaultHapticFeedbackIntensity
+                + ", mDefaultMediaIntensity=" + mDefaultMediaVibrationIntensity
+                + ", mDefaultNotificationIntensity=" + mDefaultNotificationVibrationIntensity
+                + ", mDefaultRingIntensity=" + mDefaultRingVibrationIntensity
+                + "}";
+    }
+}
diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl
index 0e32a78..5814bac 100644
--- a/core/java/android/permission/IPermissionController.aidl
+++ b/core/java/android/permission/IPermissionController.aidl
@@ -56,4 +56,6 @@
             in AndroidFuture<String> callback);
     void getUnusedAppCount(
             in AndroidFuture callback);
+    void selfRevokePermissions(in String packageName, in List<String> permissions,
+            in AndroidFuture callback);
 }
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 4a94c32..8e5581b 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -67,6 +67,8 @@
     void revokeRuntimePermission(String packageName, String permissionName, int userId,
             String reason);
 
+    void revokePostNotificationPermissionWithoutKillForTest(String packageName, int userId);
+
     boolean shouldShowRequestPermissionRationale(String packageName, String permissionName,
             int userId);
 
@@ -74,6 +76,8 @@
 
     List<SplitPermissionInfoParcelable> getSplitPermissions();
 
+    void selfRevokePermissions(String packageName, in List<String> permissions);
+
     void startOneTimePermissionSession(String packageName, int userId, long timeout,
             int importanceToResetTimer, int importanceToKeepSessionAlive);
 
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index a0788e7..47cd107 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -817,4 +817,40 @@
             }
         });
     }
+
+    /**
+     * Triggers the revocation of one or more permissions for a package, under the following
+     * conditions:
+     * <ul>
+     * <li>The package {@code packageName} must be under the same UID as the calling process
+     * (typically, the target package is the calling package).
+     * <li>Each permission in {@code permissions} must be granted to the package
+     * {@code packageName}.
+     * <li>Each permission in {@code permissions} must be a runtime permission.
+     * </ul>
+     * <p>
+     * For every permission in {@code permissions}, the entire permission group it belongs to will
+     * be revoked. This revocation happens asynchronously and kills all processes running in the
+     * same UID as {@code packageName}. It will be triggered once it is safe to do so.
+     *
+     * @param packageName The name of the package for which the permissions will be revoked.
+     * @param permissions List of permissions to be revoked.
+     *
+     * @see Context#selfRevokePermissions(Collection)
+     *
+     * @hide
+     */
+    public void selfRevokePermissions(@NonNull String packageName,
+            @NonNull List<String> permissions) {
+        mRemoteService.postAsync(service -> {
+            AndroidFuture<Void> future = new AndroidFuture<>();
+            service.selfRevokePermissions(packageName, permissions, future);
+            return future;
+        }).whenComplete((result, err) -> {
+            if (err != null) {
+                Log.e(TAG, "Failed to self revoke " + String.join(",", permissions)
+                        + " for package " + packageName, err);
+            }
+        });
+    }
 }
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index c979303..dcbab62 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -324,6 +324,27 @@
             @NonNull Consumer<String> callback) {
         throw new AbstractMethodError("Must be overridden in implementing class");
     }
+
+    /**
+     * Triggers the revocation of one or more permissions for a package. This should only be called
+     * at the request of {@code packageName}.
+     * <p>
+     * For every permission in {@code permissions}, the entire permission group it belongs to will
+     * be revoked. This revocation happens asynchronously and kills all processes running in the
+     * same UID as {@code packageName}. It will be triggered once it is safe to do so.
+     *
+     * @param packageName The name of the package for which the permissions will be revoked.
+     * @param permissions List of permissions to be revoked.
+     * @param callback Callback waiting for operation to be complete.
+     *
+     * @see PermissionManager#selfRevokePermissions(java.util.Collection)
+     */
+    @BinderThread
+    public void onSelfRevokePermissions(@NonNull String packageName,
+            @NonNull List<String> permissions, @NonNull Runnable callback) {
+        throw new AbstractMethodError("Must be overridden in implementing class");
+    }
+
     /**
      * Get a user-readable sentence, describing the set of privileges that are to be granted to a
      * companion app managing a device of the given profile.
@@ -646,6 +667,20 @@
                     callback.completeExceptionally(t);
                 }
             }
+
+            @Override
+            public void selfRevokePermissions(@NonNull String packageName,
+                    @NonNull List<String> permissions, @NonNull AndroidFuture callback) {
+                try {
+                    enforceSomePermissionsGrantedToCaller(
+                            Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
+                    Objects.requireNonNull(callback);
+                    onSelfRevokePermissions(packageName, permissions,
+                            () -> callback.complete(null));
+                } catch (Throwable t) {
+                    callback.completeExceptionally(t);
+                }
+            }
         };
     }
 }
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 3ea50e9..13941dc 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -68,6 +68,7 @@
 import com.android.internal.util.CollectionUtils;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
@@ -561,6 +562,19 @@
     }
 
     /**
+     * @see Context#selfRevokePermissions(Collection)
+     * @hide
+     */
+    public void selfRevokePermissions(@NonNull Collection<String> permissions) {
+        try {
+            mPermissionManager.selfRevokePermissions(mContext.getPackageName(),
+                    new ArrayList<String>(permissions));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Gets the state flags associated with a permission.
      *
      * @param packageName the package name for which to get the flags
@@ -1362,6 +1376,26 @@
         return false;
     }
 
+    /**
+     * Revoke the POST_NOTIFICATIONS permission, without killing the app. This method must ONLY BE
+     * USED in CTS or local tests.
+     *
+     * @param packageName The package to be revoked
+     * @param userId The user for which to revoke
+     *
+     * @hide
+     */
+    @TestApi
+    public void revokePostNotificationPermissionWithoutKillForTest(@NonNull String packageName,
+            int userId) {
+        try {
+            mPermissionManager.revokePostNotificationPermissionWithoutKillForTest(packageName,
+                    userId);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     /* @hide */
     private static int checkPermissionUncached(@Nullable String permission, int pid, int uid) {
         final IActivityManager am = ActivityManager.getService();
diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java
index 9bdfd8e..67249be 100644
--- a/core/java/android/print/PrintJobInfo.java
+++ b/core/java/android/print/PrintJobInfo.java
@@ -231,24 +231,24 @@
     }
 
     private PrintJobInfo(@NonNull Parcel parcel) {
-        mId = parcel.readParcelable(null, android.print.PrintJobId.class);
+        mId = parcel.readParcelable(null);
         mLabel = parcel.readString();
-        mPrinterId = parcel.readParcelable(null, android.print.PrinterId.class);
+        mPrinterId = parcel.readParcelable(null);
         mPrinterName = parcel.readString();
         mState = parcel.readInt();
         mAppId = parcel.readInt();
         mTag = parcel.readString();
         mCreationTime = parcel.readLong();
         mCopies = parcel.readInt();
-        Parcelable[] parcelables = parcel.readParcelableArray(null);
+        Parcelable[] parcelables = parcel.readParcelableArray(null, PageRange.class);
         if (parcelables != null) {
             mPageRanges = new PageRange[parcelables.length];
             for (int i = 0; i < parcelables.length; i++) {
                 mPageRanges[i] = (PageRange) parcelables[i];
             }
         }
-        mAttributes = (PrintAttributes) parcel.readParcelable(null, android.print.PrintAttributes.class);
-        mDocumentInfo = (PrintDocumentInfo) parcel.readParcelable(null, android.print.PrintDocumentInfo.class);
+        mAttributes = (PrintAttributes) parcel.readParcelable(null);
+        mDocumentInfo = (PrintDocumentInfo) parcel.readParcelable(null);
         mProgress = parcel.readFloat();
         mStatus = parcel.readCharSequence();
         mStatusRes = parcel.readInt();
diff --git a/core/java/android/print/PrinterId.java b/core/java/android/print/PrinterId.java
index 284e122..25260c4 100644
--- a/core/java/android/print/PrinterId.java
+++ b/core/java/android/print/PrinterId.java
@@ -48,7 +48,7 @@
     }
 
     private PrinterId(@NonNull Parcel parcel) {
-        mServiceName = Preconditions.checkNotNull((ComponentName) parcel.readParcelable(null, android.content.ComponentName.class));
+        mServiceName = Preconditions.checkNotNull((ComponentName) parcel.readParcelable(null));
         mLocalId = Preconditions.checkNotNull(parcel.readString());
     }
 
diff --git a/core/java/android/print/PrinterInfo.java b/core/java/android/print/PrinterInfo.java
index 2f93e40..8e03e3e 100644
--- a/core/java/android/print/PrinterInfo.java
+++ b/core/java/android/print/PrinterInfo.java
@@ -270,15 +270,15 @@
     private PrinterInfo(Parcel parcel) {
         // mName can be null due to unchecked set in Builder.setName and status can be invalid
         // due to unchecked set in Builder.setStatus, hence we can only check mId for a valid state
-        mId = checkPrinterId((PrinterId) parcel.readParcelable(null, android.print.PrinterId.class));
+        mId = checkPrinterId((PrinterId) parcel.readParcelable(null));
         mName = checkName(parcel.readString());
         mStatus = checkStatus(parcel.readInt());
         mDescription = parcel.readString();
-        mCapabilities = parcel.readParcelable(null, android.print.PrinterCapabilitiesInfo.class);
+        mCapabilities = parcel.readParcelable(null);
         mIconResourceId = parcel.readInt();
         mHasCustomPrinterIcon = parcel.readByte() != 0;
         mCustomPrinterIconGen = parcel.readInt();
-        mInfoIntent = parcel.readParcelable(null, android.app.PendingIntent.class);
+        mInfoIntent = parcel.readParcelable(null);
     }
 
     @Override
diff --git a/core/java/android/printservice/PrintServiceInfo.java b/core/java/android/printservice/PrintServiceInfo.java
index 3479557..0c1b61d 100644
--- a/core/java/android/printservice/PrintServiceInfo.java
+++ b/core/java/android/printservice/PrintServiceInfo.java
@@ -76,7 +76,7 @@
     public PrintServiceInfo(Parcel parcel) {
         mId = parcel.readString();
         mIsEnabled = parcel.readByte() != 0;
-        mResolveInfo = parcel.readParcelable(null, android.content.pm.ResolveInfo.class);
+        mResolveInfo = parcel.readParcelable(null);
         mSettingsActivityName = parcel.readString();
         mAddPrintersActivityName = parcel.readString();
         mAdvancedPrintOptionsActivityName = parcel.readString();
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0c8afa3..72e2863 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4603,6 +4603,43 @@
         public static final String VIBRATE_INPUT_DEVICES = "vibrate_input_devices";
 
         /**
+         * The intensity of alarm vibrations, if configurable.
+         *
+         * Not all devices are capable of changing their vibration intensity; on these devices
+         * there will likely be no difference between the various vibration intensities except for
+         * intensity 0 (off) and the rest.
+         *
+         * <b>Values:</b><br/>
+         * 0 - Vibration is disabled<br/>
+         * 1 - Weak vibrations<br/>
+         * 2 - Medium vibrations<br/>
+         * 3 - Strong vibrations
+         * @hide
+         */
+        public static final String ALARM_VIBRATION_INTENSITY =
+                "alarm_vibration_intensity";
+
+        /**
+         * The intensity of media vibrations, if configurable.
+         *
+         * This includes any vibration that is part of media, such as music, movie, soundtrack,
+         * game or animations.
+         *
+         * Not all devices are capable of changing their vibration intensity; on these devices
+         * there will likely be no difference between the various vibration intensities except for
+         * intensity 0 (off) and the rest.
+         *
+         * <b>Values:</b><br/>
+         * 0 - Vibration is disabled<br/>
+         * 1 - Weak vibrations<br/>
+         * 2 - Medium vibrations<br/>
+         * 3 - Strong vibrations
+         * @hide
+         */
+        public static final String MEDIA_VIBRATION_INTENSITY =
+                "media_vibration_intensity";
+
+        /**
          * The intensity of notification vibrations, if configurable.
          *
          * Not all devices are capable of changing their vibration intensity; on these devices
@@ -4619,6 +4656,7 @@
         @Readable
         public static final String NOTIFICATION_VIBRATION_INTENSITY =
                 "notification_vibration_intensity";
+
         /**
          * The intensity of ringtone vibrations, if configurable.
          *
@@ -4670,7 +4708,6 @@
          * 3 - Strong vibrations
          * @hide
          */
-        @Readable
         public static final String HARDWARE_HAPTIC_FEEDBACK_INTENSITY =
                 "hardware_haptic_feedback_intensity";
 
diff --git a/core/java/android/service/autofill/BatchUpdates.java b/core/java/android/service/autofill/BatchUpdates.java
index c996cc0..8eeecc2 100644
--- a/core/java/android/service/autofill/BatchUpdates.java
+++ b/core/java/android/service/autofill/BatchUpdates.java
@@ -205,7 +205,7 @@
                     builder.transformChild(ids[i], values[i]);
                 }
             }
-            final RemoteViews updates = parcel.readParcelable(null, android.widget.RemoteViews.class);
+            final RemoteViews updates = parcel.readParcelable(null);
             if (updates != null) {
                 builder.updateTemplate(updates);
             }
diff --git a/core/java/android/service/autofill/CompositeUserData.java b/core/java/android/service/autofill/CompositeUserData.java
index 55ac5a5..92952cb 100644
--- a/core/java/android/service/autofill/CompositeUserData.java
+++ b/core/java/android/service/autofill/CompositeUserData.java
@@ -197,8 +197,8 @@
                     // Always go through the builder to ensure the data ingested by
                     // the system obeys the contract of the builder to avoid attacks
                     // using specially crafted parcels.
-                    final UserData genericUserData = parcel.readParcelable(null, android.service.autofill.UserData.class);
-                    final UserData packageUserData = parcel.readParcelable(null, android.service.autofill.UserData.class);
+                    final UserData genericUserData = parcel.readParcelable(null);
+                    final UserData packageUserData = parcel.readParcelable(null);
                     return new CompositeUserData(genericUserData, packageUserData);
                 }
 
diff --git a/core/java/android/service/autofill/CustomDescription.java b/core/java/android/service/autofill/CustomDescription.java
index 690cd06..f3f912b 100644
--- a/core/java/android/service/autofill/CustomDescription.java
+++ b/core/java/android/service/autofill/CustomDescription.java
@@ -437,7 +437,7 @@
             // Always go through the builder to ensure the data ingested by
             // the system obeys the contract of the builder to avoid attacks
             // using specially crafted parcels.
-            final RemoteViews parentPresentation = parcel.readParcelable(null, android.widget.RemoteViews.class);
+            final RemoteViews parentPresentation = parcel.readParcelable(null);
             if (parentPresentation == null) return null;
 
             final Builder builder = new Builder(parentPresentation);
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index 86341a9..8539bf5 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -913,10 +913,10 @@
     public static final @NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() {
         @Override
         public Dataset createFromParcel(Parcel parcel) {
-            final RemoteViews presentation = parcel.readParcelable(null, android.widget.RemoteViews.class);
-            final InlinePresentation inlinePresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
+            final RemoteViews presentation = parcel.readParcelable(null);
+            final InlinePresentation inlinePresentation = parcel.readParcelable(null);
             final InlinePresentation inlineTooltipPresentation =
-                    parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
+                    parcel.readParcelable(null);
             final ArrayList<AutofillId> ids =
                     parcel.createTypedArrayList(AutofillId.CREATOR);
             final ArrayList<AutofillValue> values =
@@ -929,8 +929,8 @@
                     parcel.createTypedArrayList(InlinePresentation.CREATOR);
             final ArrayList<DatasetFieldFilter> filters =
                     parcel.createTypedArrayList(DatasetFieldFilter.CREATOR);
-            final ClipData fieldContent = parcel.readParcelable(null, android.content.ClipData.class);
-            final IntentSender authentication = parcel.readParcelable(null, android.content.IntentSender.class);
+            final ClipData fieldContent = parcel.readParcelable(null);
+            final IntentSender authentication = parcel.readParcelable(null);
             final String datasetId = parcel.readString();
 
             // Always go through the builder to ensure the data ingested by
@@ -1014,7 +1014,7 @@
 
             @Override
             public DatasetFieldFilter createFromParcel(Parcel parcel) {
-                return new DatasetFieldFilter((Pattern) parcel.readSerializable(java.util.regex.Pattern.class.getClassLoader(), java.util.regex.Pattern.class));
+                return new DatasetFieldFilter((Pattern) parcel.readSerializable());
             }
 
             @Override
diff --git a/core/java/android/service/autofill/DateTransformation.java b/core/java/android/service/autofill/DateTransformation.java
index df5ed4da..7340857 100644
--- a/core/java/android/service/autofill/DateTransformation.java
+++ b/core/java/android/service/autofill/DateTransformation.java
@@ -114,8 +114,8 @@
             new Parcelable.Creator<DateTransformation>() {
         @Override
         public DateTransformation createFromParcel(Parcel parcel) {
-            return new DateTransformation(parcel.readParcelable(null, android.view.autofill.AutofillId.class),
-                    (DateFormat) parcel.readSerializable(android.icu.text.DateFormat.class.getClassLoader(), android.icu.text.DateFormat.class));
+            return new DateTransformation(parcel.readParcelable(null),
+                    (DateFormat) parcel.readSerializable());
         }
 
         @Override
diff --git a/core/java/android/service/autofill/DateValueSanitizer.java b/core/java/android/service/autofill/DateValueSanitizer.java
index c7d5b79..6f7808e 100644
--- a/core/java/android/service/autofill/DateValueSanitizer.java
+++ b/core/java/android/service/autofill/DateValueSanitizer.java
@@ -111,7 +111,7 @@
             new Parcelable.Creator<DateValueSanitizer>() {
         @Override
         public DateValueSanitizer createFromParcel(Parcel parcel) {
-            return new DateValueSanitizer((DateFormat) parcel.readSerializable(android.icu.text.DateFormat.class.getClassLoader(), android.icu.text.DateFormat.class));
+            return new DateValueSanitizer((DateFormat) parcel.readSerializable());
         }
 
         @Override
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index 43bd410..af846b6 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -384,7 +384,7 @@
         byte flg = in.readByte();
         int id = in.readInt();
         List<FillContext> fillContexts = new ArrayList<>();
-        in.readParcelableList(fillContexts, FillContext.class.getClassLoader(), android.service.autofill.FillContext.class);
+        in.readParcelableList(fillContexts, FillContext.class.getClassLoader());
         Bundle clientState = (flg & 0x4) == 0 ? null : in.readBundle();
         int flags = in.readInt();
         InlineSuggestionsRequest inlineSuggestionsRequest = (flg & 0x10) == 0 ? null : (InlineSuggestionsRequest) in.readTypedObject(InlineSuggestionsRequest.CREATOR);
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index d94988e..970cb18 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -834,35 +834,35 @@
             // the system obeys the contract of the builder to avoid attacks
             // using specially crafted parcels.
             final Builder builder = new Builder();
-            final ParceledListSlice<Dataset> datasetSlice = parcel.readParcelable(null, android.content.pm.ParceledListSlice.class);
+            final ParceledListSlice<Dataset> datasetSlice = parcel.readParcelable(null);
             final List<Dataset> datasets = (datasetSlice != null) ? datasetSlice.getList() : null;
             final int datasetCount = (datasets != null) ? datasets.size() : 0;
             for (int i = 0; i < datasetCount; i++) {
                 builder.addDataset(datasets.get(i));
             }
-            builder.setSaveInfo(parcel.readParcelable(null, android.service.autofill.SaveInfo.class));
-            builder.setClientState(parcel.readParcelable(null, android.os.Bundle.class));
+            builder.setSaveInfo(parcel.readParcelable(null));
+            builder.setClientState(parcel.readParcelable(null));
 
             // Sets authentication state.
             final AutofillId[] authenticationIds = parcel.readParcelableArray(null,
                     AutofillId.class);
-            final IntentSender authentication = parcel.readParcelable(null, android.content.IntentSender.class);
-            final RemoteViews presentation = parcel.readParcelable(null, android.widget.RemoteViews.class);
-            final InlinePresentation inlinePresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
-            final InlinePresentation inlineTooltipPresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
+            final IntentSender authentication = parcel.readParcelable(null);
+            final RemoteViews presentation = parcel.readParcelable(null);
+            final InlinePresentation inlinePresentation = parcel.readParcelable(null);
+            final InlinePresentation inlineTooltipPresentation = parcel.readParcelable(null);
             if (authenticationIds != null) {
                 builder.setAuthentication(authenticationIds, authentication, presentation,
                         inlinePresentation, inlineTooltipPresentation);
             }
-            final RemoteViews header = parcel.readParcelable(null, android.widget.RemoteViews.class);
+            final RemoteViews header = parcel.readParcelable(null);
             if (header != null) {
                 builder.setHeader(header);
             }
-            final RemoteViews footer = parcel.readParcelable(null, android.widget.RemoteViews.class);
+            final RemoteViews footer = parcel.readParcelable(null);
             if (footer != null) {
                 builder.setFooter(footer);
             }
-            final UserData userData = parcel.readParcelable(null, android.service.autofill.UserData.class);
+            final UserData userData = parcel.readParcelable(null);
             if (userData != null) {
                 builder.setUserData(userData);
             }
diff --git a/core/java/android/service/autofill/ImageTransformation.java b/core/java/android/service/autofill/ImageTransformation.java
index af82205..e317159 100644
--- a/core/java/android/service/autofill/ImageTransformation.java
+++ b/core/java/android/service/autofill/ImageTransformation.java
@@ -247,7 +247,7 @@
             new Parcelable.Creator<ImageTransformation>() {
         @Override
         public ImageTransformation createFromParcel(Parcel parcel) {
-            final AutofillId id = parcel.readParcelable(null, android.view.autofill.AutofillId.class);
+            final AutofillId id = parcel.readParcelable(null);
 
             final Pattern[] regexs = (Pattern[]) parcel.readSerializable();
             final int[] resIds = parcel.createIntArray();
diff --git a/core/java/android/service/autofill/NegationValidator.java b/core/java/android/service/autofill/NegationValidator.java
index 85cd981..d626845 100644
--- a/core/java/android/service/autofill/NegationValidator.java
+++ b/core/java/android/service/autofill/NegationValidator.java
@@ -68,7 +68,7 @@
             new Parcelable.Creator<NegationValidator>() {
         @Override
         public NegationValidator createFromParcel(Parcel parcel) {
-            return new NegationValidator(parcel.readParcelable(null, android.service.autofill.InternalValidator.class));
+            return new NegationValidator(parcel.readParcelable(null));
         }
 
         @Override
diff --git a/core/java/android/service/autofill/RegexValidator.java b/core/java/android/service/autofill/RegexValidator.java
index 4c58590..00c4347 100644
--- a/core/java/android/service/autofill/RegexValidator.java
+++ b/core/java/android/service/autofill/RegexValidator.java
@@ -96,8 +96,8 @@
             new Parcelable.Creator<RegexValidator>() {
         @Override
         public RegexValidator createFromParcel(Parcel parcel) {
-            return new RegexValidator(parcel.readParcelable(null, android.view.autofill.AutofillId.class),
-                    (Pattern) parcel.readSerializable(java.util.regex.Pattern.class.getClassLoader(), java.util.regex.Pattern.class));
+            return new RegexValidator(parcel.readParcelable(null),
+                    (Pattern) parcel.readSerializable());
         }
 
         @Override
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index 5fe1d4f..8edfde8 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -888,14 +888,14 @@
                 builder.setOptionalIds(optionalIds);
             }
 
-            builder.setNegativeAction(parcel.readInt(), parcel.readParcelable(null, android.content.IntentSender.class));
+            builder.setNegativeAction(parcel.readInt(), parcel.readParcelable(null));
             builder.setPositiveAction(parcel.readInt());
             builder.setDescription(parcel.readCharSequence());
-            final CustomDescription customDescripton = parcel.readParcelable(null, android.service.autofill.CustomDescription.class);
+            final CustomDescription customDescripton = parcel.readParcelable(null);
             if (customDescripton != null) {
                 builder.setCustomDescription(customDescripton);
             }
-            final InternalValidator validator = parcel.readParcelable(null, android.service.autofill.InternalValidator.class);
+            final InternalValidator validator = parcel.readParcelable(null);
             if (validator != null) {
                 builder.setValidator(validator);
             }
@@ -909,7 +909,7 @@
                     builder.addSanitizer(sanitizers[i], autofillIds);
                 }
             }
-            final AutofillId triggerId = parcel.readParcelable(null, android.view.autofill.AutofillId.class);
+            final AutofillId triggerId = parcel.readParcelable(null);
             if (triggerId != null) {
                 builder.setTriggerId(triggerId);
             }
diff --git a/core/java/android/service/autofill/TextValueSanitizer.java b/core/java/android/service/autofill/TextValueSanitizer.java
index 46c18b2..5bafa7a 100644
--- a/core/java/android/service/autofill/TextValueSanitizer.java
+++ b/core/java/android/service/autofill/TextValueSanitizer.java
@@ -119,7 +119,7 @@
             new Parcelable.Creator<TextValueSanitizer>() {
         @Override
         public TextValueSanitizer createFromParcel(Parcel parcel) {
-            return new TextValueSanitizer((Pattern) parcel.readSerializable(java.util.regex.Pattern.class.getClassLoader(), java.util.regex.Pattern.class), parcel.readString());
+            return new TextValueSanitizer((Pattern) parcel.readSerializable(), parcel.readString());
         }
 
         @Override
diff --git a/core/java/android/service/contentcapture/ActivityEvent.java b/core/java/android/service/contentcapture/ActivityEvent.java
index d286942..74a7355 100644
--- a/core/java/android/service/contentcapture/ActivityEvent.java
+++ b/core/java/android/service/contentcapture/ActivityEvent.java
@@ -149,7 +149,7 @@
         @Override
         @NonNull
         public ActivityEvent createFromParcel(@NonNull Parcel parcel) {
-            final ComponentName componentName = parcel.readParcelable(null, android.content.ComponentName.class);
+            final ComponentName componentName = parcel.readParcelable(null);
             final int eventType = parcel.readInt();
             return new ActivityEvent(componentName, eventType);
         }
diff --git a/core/java/android/service/contentcapture/SnapshotData.java b/core/java/android/service/contentcapture/SnapshotData.java
index f72624d..bf469b4 100644
--- a/core/java/android/service/contentcapture/SnapshotData.java
+++ b/core/java/android/service/contentcapture/SnapshotData.java
@@ -51,8 +51,8 @@
 
     SnapshotData(@NonNull Parcel parcel) {
         mAssistData = parcel.readBundle();
-        mAssistStructure = parcel.readParcelable(null, android.app.assist.AssistStructure.class);
-        mAssistContent = parcel.readParcelable(null, android.app.assist.AssistContent.class);
+        mAssistStructure = parcel.readParcelable(null);
+        mAssistContent = parcel.readParcelable(null);
     }
 
     /**
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index 50f9d8a..163d6ed 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -35,6 +35,7 @@
 public abstract class DreamOverlayService extends Service {
     private static final String TAG = "DreamOverlayService";
     private static final boolean DEBUG = false;
+    private boolean mShowComplications;
 
     private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
         @Override
@@ -53,6 +54,8 @@
     @Nullable
     @Override
     public final IBinder onBind(@NonNull Intent intent) {
+        mShowComplications = intent.getBooleanExtra(DreamService.EXTRA_SHOW_COMPLICATIONS,
+                DreamService.DEFAULT_SHOW_COMPLICATIONS);
         return mDreamOverlay.asBinder();
     }
 
@@ -74,4 +77,11 @@
             Log.e(TAG, "Could not request exit:" + e);
         }
     }
+
+    /**
+     * Returns whether to show complications on the dream overlay.
+     */
+    public final boolean shouldShowComplications() {
+        return mShowComplications;
+    }
 }
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 4ae3d7d..133e384 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -189,6 +189,19 @@
      */
     public static final String DREAM_META_DATA = "android.service.dream";
 
+    /**
+     * Extra containing a boolean for whether to show complications on the overlay.
+     * @hide
+     */
+    public static final String EXTRA_SHOW_COMPLICATIONS =
+            "android.service.dreams.SHOW_COMPLICATIONS";
+
+    /**
+     * The default value for whether to show complications on the overlay.
+     * @hide
+     */
+    public static final boolean DEFAULT_SHOW_COMPLICATIONS = true;
+
     private final IDreamManager mDreamManager;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private IBinder mDreamToken;
diff --git a/core/java/android/service/games/GameService.java b/core/java/android/service/games/GameService.java
index b79c055..105c2aa 100644
--- a/core/java/android/service/games/GameService.java
+++ b/core/java/android/service/games/GameService.java
@@ -38,10 +38,23 @@
  * when a game session should begin. It is always kept running by the system.
  * Because of this it should be kept as lightweight as possible.
  *
- * Heavy weight operations (such as showing UI) should be implemented in the
+ * <p>Heavyweight operations (such as showing UI) should be implemented in the
  * associated {@link GameSessionService} when a game session is taking place. Its
  * implementation should run in a separate process from the {@link GameService}.
  *
+ * <p>To extend this class, you must declare the service in your manifest file with
+ * the {@link android.Manifest.permission#BIND_GAME_SERVICE} permission
+ * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:
+ * <pre>
+ * &lt;service android:name=".GameService"
+ *          android:label="&#64;string/service_name"
+ *          android:permission="android.permission.BIND_GAME_SERVICE">
+ *     &lt;intent-filter>
+ *         &lt;action android:name="android.service.games.GameService" />
+ *     &lt;/intent-filter>
+ * &lt;/service>
+ * </pre>
+ *
  * @hide
  */
 @SystemApi
@@ -90,7 +103,7 @@
 
     @Override
     @Nullable
-    public IBinder onBind(@Nullable Intent intent) {
+    public final IBinder onBind(@Nullable Intent intent) {
         if (ACTION_GAME_SERVICE.equals(intent.getAction())) {
             return mInterface.asBinder();
         }
diff --git a/core/java/android/service/games/GameSessionService.java b/core/java/android/service/games/GameSessionService.java
index a2c8870..c1a3eb5 100644
--- a/core/java/android/service/games/GameSessionService.java
+++ b/core/java/android/service/games/GameSessionService.java
@@ -73,7 +73,7 @@
 
     @Override
     @Nullable
-    public IBinder onBind(@Nullable Intent intent) {
+    public final IBinder onBind(@Nullable Intent intent) {
         if (intent == null) {
             return null;
         }
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
index 267b2ff..4f324f9 100644
--- a/core/java/android/service/notification/Condition.java
+++ b/core/java/android/service/notification/Condition.java
@@ -114,7 +114,7 @@
     }
 
     public Condition(Parcel source) {
-        this((Uri)source.readParcelable(Condition.class.getClassLoader(), android.net.Uri.class),
+        this((Uri)source.readParcelable(Condition.class.getClassLoader()),
                 source.readString(),
                 source.readString(),
                 source.readString(),
diff --git a/core/java/android/service/notification/ConversationChannelWrapper.java b/core/java/android/service/notification/ConversationChannelWrapper.java
index 35b6bad..3d0984c 100644
--- a/core/java/android/service/notification/ConversationChannelWrapper.java
+++ b/core/java/android/service/notification/ConversationChannelWrapper.java
@@ -40,10 +40,10 @@
     public ConversationChannelWrapper() {}
 
     protected ConversationChannelWrapper(Parcel in) {
-        mNotificationChannel = in.readParcelable(NotificationChannel.class.getClassLoader(), android.app.NotificationChannel.class);
+        mNotificationChannel = in.readParcelable(NotificationChannel.class.getClassLoader());
         mGroupLabel = in.readCharSequence();
         mParentChannelLabel = in.readCharSequence();
-        mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader(), android.content.pm.ShortcutInfo.class);
+        mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader());
         mPkg = in.readStringNoHelper();
         mUid = in.readInt();
     }
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index ae39d3d..c945954 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1763,7 +1763,7 @@
             mImportanceExplanation = in.readCharSequence(); // may be null
             mRankingScore = in.readFloat();
             mOverrideGroupKey = in.readString(); // may be null
-            mChannel = in.readParcelable(cl, android.app.NotificationChannel.class); // may be null
+            mChannel = in.readParcelable(cl); // may be null
             mOverridePeople = in.createStringArrayList();
             mSnoozeCriteria = in.createTypedArrayList(SnoozeCriterion.CREATOR);
             mShowBadge = in.readBoolean();
@@ -1776,7 +1776,7 @@
             mCanBubble = in.readBoolean();
             mIsTextChanged = in.readBoolean();
             mIsConversation = in.readBoolean();
-            mShortcutInfo = in.readParcelable(cl, android.content.pm.ShortcutInfo.class);
+            mShortcutInfo = in.readParcelable(cl);
             mRankingAdjustment = in.readInt();
             mIsBubble = in.readBoolean();
         }
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index a853714..c64f4c46 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -30,7 +30,7 @@
     }
 
     public NotificationRankingUpdate(Parcel in) {
-        mRankingMap = in.readParcelable(getClass().getClassLoader(), android.service.notification.NotificationListenerService.RankingMap.class);
+        mRankingMap = in.readParcelable(getClass().getClassLoader());
     }
 
     public NotificationListenerService.RankingMap getRankingMap() {
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 8834cee..c1d5a28 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -211,7 +211,7 @@
         allowCallsFrom = source.readInt();
         allowMessagesFrom = source.readInt();
         user = source.readInt();
-        manualRule = source.readParcelable(null, android.service.notification.ZenModeConfig.ZenRule.class);
+        manualRule = source.readParcelable(null);
         final int len = source.readInt();
         if (len > 0) {
             final String[] ids = new String[len];
@@ -1800,10 +1800,10 @@
                 name = source.readString();
             }
             zenMode = source.readInt();
-            conditionId = source.readParcelable(null, android.net.Uri.class);
-            condition = source.readParcelable(null, android.service.notification.Condition.class);
-            component = source.readParcelable(null, android.content.ComponentName.class);
-            configurationActivity = source.readParcelable(null, android.content.ComponentName.class);
+            conditionId = source.readParcelable(null);
+            condition = source.readParcelable(null);
+            component = source.readParcelable(null);
+            configurationActivity = source.readParcelable(null);
             if (source.readInt() == 1) {
                 id = source.readString();
             }
@@ -1811,7 +1811,7 @@
             if (source.readInt() == 1) {
                 enabler = source.readString();
             }
-            zenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class);
+            zenPolicy = source.readParcelable(null);
             modified = source.readInt() == 1;
             pkg = source.readString();
         }
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index a04f073..ed3a9ac 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -804,8 +804,8 @@
         @Override
         public ZenPolicy createFromParcel(Parcel source) {
             ZenPolicy policy = new ZenPolicy();
-            policy.mPriorityCategories = source.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class);
-            policy.mVisualEffects = source.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class);
+            policy.mPriorityCategories = source.readArrayList(Integer.class.getClassLoader());
+            policy.mVisualEffects = source.readArrayList(Integer.class.getClassLoader());
             policy.mPriorityCalls = source.readInt();
             policy.mPriorityMessages = source.readInt();
             policy.mConversationSenders = source.readInt();
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java b/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java
index 7471a4f..0551e27 100644
--- a/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java
@@ -63,7 +63,7 @@
     private static GetWalletCardsResponse readFromParcel(Parcel source) {
         int size = source.readInt();
         List<WalletCard> walletCards =
-                source.readParcelableList(new ArrayList<>(size), WalletCard.class.getClassLoader(), android.service.quickaccesswallet.WalletCard.class);
+                source.readParcelableList(new ArrayList<>(size), WalletCard.class.getClassLoader());
         int selectedIndex = source.readInt();
         return new GetWalletCardsResponse(walletCards, selectedIndex);
     }
diff --git a/core/java/android/service/settings/suggestions/Suggestion.java b/core/java/android/service/settings/suggestions/Suggestion.java
index 16622d7..3e63efb 100644
--- a/core/java/android/service/settings/suggestions/Suggestion.java
+++ b/core/java/android/service/settings/suggestions/Suggestion.java
@@ -120,9 +120,9 @@
         mId = in.readString();
         mTitle = in.readCharSequence();
         mSummary = in.readCharSequence();
-        mIcon = in.readParcelable(Icon.class.getClassLoader(), android.graphics.drawable.Icon.class);
+        mIcon = in.readParcelable(Icon.class.getClassLoader());
         mFlags = in.readInt();
-        mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader(), android.app.PendingIntent.class);
+        mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader());
     }
 
     public static final @android.annotation.NonNull Creator<Suggestion> CREATOR = new Creator<Suggestion>() {
diff --git a/core/java/android/service/timezone/TimeZoneProviderEvent.java b/core/java/android/service/timezone/TimeZoneProviderEvent.java
index f6433b7..7005281 100644
--- a/core/java/android/service/timezone/TimeZoneProviderEvent.java
+++ b/core/java/android/service/timezone/TimeZoneProviderEvent.java
@@ -141,7 +141,7 @@
                     int type = in.readInt();
                     long creationElapsedMillis = in.readLong();
                     TimeZoneProviderSuggestion suggestion =
-                            in.readParcelable(getClass().getClassLoader(), android.service.timezone.TimeZoneProviderSuggestion.class);
+                            in.readParcelable(getClass().getClassLoader());
                     String failureCause = in.readString8();
                     return new TimeZoneProviderEvent(
                             type, creationElapsedMillis, suggestion, failureCause);
diff --git a/core/java/android/service/timezone/TimeZoneProviderSuggestion.java b/core/java/android/service/timezone/TimeZoneProviderSuggestion.java
index 4841ac1..229fa26 100644
--- a/core/java/android/service/timezone/TimeZoneProviderSuggestion.java
+++ b/core/java/android/service/timezone/TimeZoneProviderSuggestion.java
@@ -100,7 +100,7 @@
                 public TimeZoneProviderSuggestion createFromParcel(Parcel in) {
                     @SuppressWarnings("unchecked")
                     ArrayList<String> timeZoneIds =
-                            (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class);
+                            (ArrayList<String>) in.readArrayList(null /* classLoader */);
                     long elapsedRealtimeMillis = in.readLong();
                     return new TimeZoneProviderSuggestion(timeZoneIds, elapsedRealtimeMillis);
                 }
diff --git a/core/java/android/speech/tts/Voice.java b/core/java/android/speech/tts/Voice.java
index 0d98a6c..7ffe5eb 100644
--- a/core/java/android/speech/tts/Voice.java
+++ b/core/java/android/speech/tts/Voice.java
@@ -84,7 +84,7 @@
 
     private Voice(Parcel in) {
         this.mName = in.readString();
-        this.mLocale = (Locale)in.readSerializable(java.util.Locale.class.getClassLoader(), java.util.Locale.class);
+        this.mLocale = (Locale)in.readSerializable();
         this.mQuality = in.readInt();
         this.mLatency = in.readInt();
         this.mRequiresNetworkConnection = (in.readByte() == 1);
diff --git a/core/java/android/telephony/SubscriptionPlan.java b/core/java/android/telephony/SubscriptionPlan.java
index fb2d771..d5ac436 100644
--- a/core/java/android/telephony/SubscriptionPlan.java
+++ b/core/java/android/telephony/SubscriptionPlan.java
@@ -99,7 +99,7 @@
     }
 
     private SubscriptionPlan(Parcel source) {
-        cycleRule = source.readParcelable(null, android.util.RecurrenceRule.class);
+        cycleRule = source.readParcelable(null);
         title = source.readCharSequence();
         summary = source.readCharSequence();
         dataLimitBytes = source.readLong();
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 9eaaa91..542de3f 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -131,6 +131,7 @@
                     mContext.getAttributionTag(), callback);
         } catch (RemoteException ex) {
             // system server crash
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -152,6 +153,7 @@
             mSubscriptionChangedListenerMap.remove(listener);
         } catch (RemoteException ex) {
             // system server crash
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -194,6 +196,7 @@
                     mContext.getAttributionTag(), callback);
         } catch (RemoteException ex) {
             // system server crash
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -216,6 +219,7 @@
             mOpportunisticSubscriptionChangedListenerMap.remove(listener);
         } catch (RemoteException ex) {
             // system server crash
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -304,6 +308,7 @@
             sRegistry.notifyCarrierNetworkChange(active);
         } catch (RemoteException ex) {
             // system server crash
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -329,6 +334,7 @@
             sRegistry.notifyCarrierNetworkChangeWithSubId(subscriptionId, active);
         } catch (RemoteException ex) {
             // system server crash
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -347,6 +353,7 @@
             sRegistry.notifyCallState(slotIndex, subId, state, incomingNumber);
         } catch (RemoteException ex) {
             // system server crash
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -364,6 +371,7 @@
             sRegistry.notifyCallStateForAllSubs(state, incomingNumber);
         } catch (RemoteException ex) {
             // system server crash
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -376,6 +384,7 @@
             sRegistry.notifySubscriptionInfoChanged();
         } catch (RemoteException ex) {
             // system server crash
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -388,6 +397,7 @@
             sRegistry.notifyOpportunisticSubscriptionInfoChanged();
         } catch (RemoteException ex) {
             // system server crash
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -404,6 +414,7 @@
             sRegistry.notifyServiceStateForPhoneId(slotIndex, subId, state);
         } catch (RemoteException ex) {
             // system server crash
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -421,6 +432,7 @@
             sRegistry.notifySignalStrengthForPhoneId(slotIndex, subId, signalStrength);
         } catch (RemoteException ex) {
             // system server crash
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -439,6 +451,7 @@
             sRegistry.notifyMessageWaitingChangedForPhoneId(slotIndex, subId, msgWaitingInd);
         } catch (RemoteException ex) {
             // system process is dead
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -454,6 +467,7 @@
             sRegistry.notifyCallForwardingChangedForSubscriber(subId, callForwardInd);
         } catch (RemoteException ex) {
             // system process is dead
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -469,6 +483,7 @@
             sRegistry.notifyDataActivityForSubscriber(subId, dataActivityType);
         } catch (RemoteException ex) {
             // system process is dead
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -490,6 +505,7 @@
                     slotIndex, subId, preciseState);
         } catch (RemoteException ex) {
             // system process is dead
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -508,6 +524,7 @@
             sRegistry.notifyCallQualityChanged(callQuality, slotIndex, subId, networkType);
         } catch (RemoteException ex) {
             // system process is dead
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -523,6 +540,7 @@
             sRegistry.notifyEmergencyNumberList(slotIndex, subId);
         } catch (RemoteException ex) {
             // system process is dead
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -538,6 +556,7 @@
             sRegistry.notifyOutgoingEmergencyCall(phoneId, subId, emergencyNumber);
         } catch (RemoteException ex) {
             // system process is dead
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -553,6 +572,7 @@
             sRegistry.notifyOutgoingEmergencySms(phoneId, subId, emergencyNumber);
         } catch (RemoteException ex) {
             // system process is dead
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -570,6 +590,7 @@
             sRegistry.notifyRadioPowerStateChanged(slotIndex, subId, radioPowerState);
         } catch (RemoteException ex) {
             // system process is dead
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -583,6 +604,7 @@
             sRegistry.notifyPhoneCapabilityChanged(phoneCapability);
         } catch (RemoteException ex) {
             // system process is dead
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -615,6 +637,7 @@
                     SIM_ACTIVATION_TYPE_DATA, activationState);
         } catch (RemoteException ex) {
             // system process is dead
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -634,6 +657,7 @@
                     SIM_ACTIVATION_TYPE_VOICE, activationState);
         } catch (RemoteException ex) {
             // system process is dead
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -651,6 +675,7 @@
             sRegistry.notifyUserMobileDataStateChangedForPhoneId(slotIndex, subId, state);
         } catch (RemoteException ex) {
             // system process is dead
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -669,6 +694,7 @@
             sRegistry.notifyDisplayInfoChanged(slotIndex, subscriptionId, telephonyDisplayInfo);
         } catch (RemoteException ex) {
             // system process is dead
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -683,6 +709,7 @@
             sRegistry.notifyImsDisconnectCause(subId, imsReasonInfo);
         } catch (RemoteException ex) {
             // system process is dead
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -698,6 +725,7 @@
             sRegistry.notifySrvccStateChanged(subId, state);
         } catch (RemoteException ex) {
             // system process is dead
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -721,6 +749,7 @@
                 foregroundCallPreciseState, backgroundCallPreciseState);
         } catch (RemoteException ex) {
             // system process is dead
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -741,6 +770,7 @@
             sRegistry.notifyDisconnectCause(slotIndex, subId, cause, preciseCause);
         } catch (RemoteException ex) {
             // system process is dead
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -755,6 +785,7 @@
             sRegistry.notifyCellLocationForSubscriber(subId, cellLocation);
         } catch (RemoteException ex) {
             // system process is dead
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -769,7 +800,7 @@
         try {
             sRegistry.notifyCellInfoForSubscriber(subId, cellInfo);
         } catch (RemoteException ex) {
-
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -781,7 +812,7 @@
         try {
             sRegistry.notifyActiveDataSubIdChanged(activeDataSubId);
         } catch (RemoteException ex) {
-
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -814,6 +845,7 @@
             sRegistry.notifyRegistrationFailed(slotIndex, subId, cellIdentity,
                     chosenPlmn, domain, causeCode, additionalCauseCode);
         } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -830,6 +862,7 @@
             sRegistry.notifyBarringInfoChanged(slotIndex, subId, barringInfo);
         } catch (RemoteException ex) {
             // system server crash
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -846,6 +879,7 @@
             sRegistry.notifyPhysicalChannelConfigForSubscriber(slotIndex, subId, configs);
         } catch (RemoteException ex) {
             // system server crash
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -862,6 +896,7 @@
             sRegistry.notifyDataEnabled(slotIndex, subId, enabled, reason);
         } catch (RemoteException ex) {
             // system server crash
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -880,6 +915,7 @@
                     allowedNetworkType);
         } catch (RemoteException ex) {
             // system process is dead
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -895,6 +931,7 @@
             sRegistry.notifyLinkCapacityEstimateChanged(slotIndex, subId, linkCapacityEstimateList);
         } catch (RemoteException ex) {
             // system server crash
+            throw ex.rethrowFromSystemServer();
         }
     }
 
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 3ee1a90..fee23f4 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -16,6 +16,9 @@
 
 package android.text;
 
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Canvas;
 import android.graphics.Paint;
@@ -85,6 +88,41 @@
     }
 
     /**
+     * Utility function to construct a BoringLayout instance.
+     *
+     * The spacing multiplier and additional amount spacing are not used by BoringLayout.
+     * {@link Layout#getSpacingMultiplier()} will return 1.0 and {@link Layout#getSpacingAdd()} will
+     * return 0.0.
+     *
+     * @param source the text to render
+     * @param paint the default paint for the layout
+     * @param outerWidth the wrapping width for the text
+     * @param align whether to left, right, or center the text
+     * @param metrics {@code #Metrics} instance that contains information about FontMetrics and
+     *                line width
+     * @param includePad set whether to include extra space beyond font ascent and descent which is
+     *                   needed to avoid clipping in some scripts
+     * @param ellipsize whether to ellipsize the text if width of the text is longer than the
+     *                  requested width
+     * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is
+     *                        {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is
+     *                        not used, {@code outerWidth} is used instead
+     * @param useFallbackLineSpacing True for adjusting the line spacing based on fallback fonts.
+     *                              False for keeping the first font's line height. If some glyphs
+     *                              requires larger vertical spaces, by passing true to this
+     *                              argument, the layout increase the line height to fit all glyphs.
+     */
+    public static @NonNull BoringLayout make(
+            @NonNull CharSequence source, @NonNull TextPaint paint,
+            @IntRange(from = 0) int outerWidth,
+            @NonNull Alignment align, @NonNull BoringLayout.Metrics metrics,
+            boolean includePad, @NonNull TextUtils.TruncateAt ellipsize,
+            @IntRange(from = 0) int ellipsizedWidth, boolean useFallbackLineSpacing) {
+        return new BoringLayout(source, paint, outerWidth, align, 1f, 0f, metrics, includePad,
+                ellipsize, ellipsizedWidth, useFallbackLineSpacing);
+    }
+
+    /**
      * Returns a BoringLayout for the specified text, potentially reusing
      * this one if it is already suitable.  The caller must make sure that
      * no one is still using this Layout.
@@ -109,7 +147,61 @@
         mEllipsizedStart = 0;
         mEllipsizedCount = 0;
 
-        init(source, paint, align, metrics, includePad, true);
+        init(source, paint, align, metrics, includePad, true, false /* useFallbackLineSpacing */);
+        return this;
+    }
+
+    /**
+     * Returns a BoringLayout for the specified text, potentially reusing
+     * this one if it is already suitable.  The caller must make sure that
+     * no one is still using this Layout.
+     *
+     * The spacing multiplier and additional amount spacing are not used by BoringLayout.
+     * {@link Layout#getSpacingMultiplier()} will return 1.0 and {@link Layout#getSpacingAdd()} will
+     * return 0.0.
+     *
+     * @param source the text to render
+     * @param paint the default paint for the layout
+     * @param outerWidth the wrapping width for the text
+     * @param align whether to left, right, or center the text
+     * @param metrics {@code #Metrics} instance that contains information about FontMetrics and
+     *                line width
+     * @param includePad set whether to include extra space beyond font ascent and descent which is
+     *                   needed to avoid clipping in some scripts
+     * @param ellipsize whether to ellipsize the text if width of the text is longer than the
+     *                  requested width
+     * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is
+     *                        {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is
+     *                        not used, {@code outerWidth} is used instead
+     * @param useFallbackLineSpacing True for adjusting the line spacing based on fallback fonts.
+     *                              False for keeping the first font's line height. If some glyphs
+     *                              requires larger vertical spaces, by passing true to this
+     *                              argument, the layout increase the line height to fit all glyphs.
+     */
+    public @NonNull BoringLayout replaceOrMake(@NonNull CharSequence source,
+            @NonNull TextPaint paint, @IntRange(from = 0) int outerWidth,
+            @NonNull Alignment align, @NonNull BoringLayout.Metrics metrics, boolean includePad,
+            @NonNull TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth,
+            boolean useFallbackLineSpacing) {
+        boolean trust;
+
+        if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {
+            replaceWith(source, paint, outerWidth, align, 1f, 0f);
+
+            mEllipsizedWidth = outerWidth;
+            mEllipsizedStart = 0;
+            mEllipsizedCount = 0;
+            trust = true;
+        } else {
+            replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth, ellipsize, true, this),
+                    paint, outerWidth, align, 1f, 0f);
+
+            mEllipsizedWidth = ellipsizedWidth;
+            trust = false;
+        }
+
+        init(getText(), paint, align, metrics, includePad, trust,
+                useFallbackLineSpacing);
         return this;
     }
 
@@ -132,30 +224,13 @@
      *                  requested width
      * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is
      *                        {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is
-     *                        not used, {@code outerwidth} is used instead
+     *                        not used, {@code outerWidth} is used instead
      */
     public BoringLayout replaceOrMake(CharSequence source, TextPaint paint, int outerWidth,
             Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics,
             boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
-        boolean trust;
-
-        if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {
-            replaceWith(source, paint, outerWidth, align, spacingMult, spacingAdd);
-
-            mEllipsizedWidth = outerWidth;
-            mEllipsizedStart = 0;
-            mEllipsizedCount = 0;
-            trust = true;
-        } else {
-            replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth, ellipsize, true, this),
-                    paint, outerWidth, align, spacingMult, spacingAdd);
-
-            mEllipsizedWidth = ellipsizedWidth;
-            trust = false;
-        }
-
-        init(getText(), paint, align, metrics, includePad, trust);
-        return this;
+        return replaceOrMake(source, paint, outerWidth, align, metrics,
+                includePad, ellipsize, ellipsizedWidth, false /* useFallbackLineSpacing */);
     }
 
     /**
@@ -178,7 +253,7 @@
         mEllipsizedStart = 0;
         mEllipsizedCount = 0;
 
-        init(source, paint, align, metrics, includePad, true);
+        init(source, paint, align, metrics, includePad, true, false /* useFallbackLineSpacing */);
     }
 
     /**
@@ -194,14 +269,46 @@
      * @param includePad set whether to include extra space beyond font ascent and descent which is
      *                   needed to avoid clipping in some scripts
      * @param ellipsize whether to ellipsize the text if width of the text is longer than the
-     *                  requested {@code outerwidth}
+     *                  requested {@code outerWidth}
      * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is
      *                        {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is
-     *                        not used, {@code outerwidth} is used instead
+     *                        not used, {@code outerWidth} is used instead
      */
     public BoringLayout(CharSequence source, TextPaint paint, int outerWidth, Alignment align,
             float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad,
             TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
+        this(source, paint, outerWidth, align, spacingMult, spacingAdd, metrics, includePad,
+                ellipsize, ellipsizedWidth, false /* fallbackLineSpacing */);
+    }
+
+    /**
+     *
+     * @param source the text to render
+     * @param paint the default paint for the layout
+     * @param outerWidth the wrapping width for the text
+     * @param align whether to left, right, or center the text
+     * @param spacingMult this value is no longer used by BoringLayout
+     * @param spacingAdd this value is no longer used by BoringLayout
+     * @param metrics {@code #Metrics} instance that contains information about FontMetrics and
+     *                line width
+     * @param includePad set whether to include extra space beyond font ascent and descent which is
+     *                   needed to avoid clipping in some scripts
+     * @param ellipsize whether to ellipsize the text if width of the text is longer than the
+     *                  requested {@code outerWidth}
+     * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is
+     *                        {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is
+     *                        not used, {@code outerWidth} is used instead
+     * @param useFallbackLineSpacing True for adjusting the line spacing based on fallback fonts.
+     *                              False for keeping the first font's line height. If some glyphs
+     *                              requires larger vertical spaces, by passing true to this
+     *                              argument, the layout increase the line height to fit all glyphs.
+     */
+    public BoringLayout(
+            @NonNull CharSequence source, @NonNull TextPaint paint,
+            @IntRange(from = 0) int outerWidth, @NonNull Alignment align, float spacingMult,
+            float spacingAdd, @NonNull BoringLayout.Metrics metrics, boolean includePad,
+            @NonNull TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth,
+            boolean useFallbackLineSpacing) {
         /*
          * It is silly to have to call super() and then replaceWith(),
          * but we can't use "this" for the callback until the call to
@@ -224,11 +331,12 @@
             trust = false;
         }
 
-        init(getText(), paint, align, metrics, includePad, trust);
+        init(getText(), paint, align, metrics, includePad, trust, useFallbackLineSpacing);
     }
 
     /* package */ void init(CharSequence source, TextPaint paint, Alignment align,
-            BoringLayout.Metrics metrics, boolean includePad, boolean trustWidth) {
+            BoringLayout.Metrics metrics, boolean includePad, boolean trustWidth,
+            boolean useFallbackLineSpacing) {
         int spacing;
 
         if (source instanceof String && align == Layout.Alignment.ALIGN_NORMAL) {
@@ -260,7 +368,7 @@
             TextLine line = TextLine.obtain();
             line.set(paint, source, 0, source.length(), Layout.DIR_LEFT_TO_RIGHT,
                     Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
-                    mEllipsizedStart, mEllipsizedStart + mEllipsizedCount);
+                    mEllipsizedStart, mEllipsizedStart + mEllipsizedCount, useFallbackLineSpacing);
             mMax = (int) Math.ceil(line.metrics(null));
             TextLine.recycle(line);
         }
@@ -336,6 +444,27 @@
     @UnsupportedAppUsage
     public static Metrics isBoring(CharSequence text, TextPaint paint,
             TextDirectionHeuristic textDir, Metrics metrics) {
+        return isBoring(text, paint, textDir, false /* useFallbackLineSpacing */, metrics);
+    }
+
+    /**
+     * Returns null if not boring; the width, ascent, and descent in the
+     * provided Metrics object (or a new one if the provided one was null)
+     * if boring.
+     *
+     * @param text a text to be calculated text layout.
+     * @param paint a paint object used for styling.
+     * @param textDir a text direction.
+     * @param useFallbackLineSpacing True for adjusting the line spacing based on fallback fonts.
+     *                              False for keeping the first font's line height. If some glyphs
+     *                              requires larger vertical spaces, by passing true to this
+     *                              argument, the layout increase the line height to fit all glyphs.
+     * @param metrics the out metrics.
+     * @return metrics on success. null if text cannot be rendered by BoringLayout.
+     */
+    public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint,
+            @NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing,
+            @Nullable Metrics metrics) {
         final int textLength = text.length();
         if (hasAnyInterestingChars(text, textLength)) {
            return null;  // There are some interesting characters. Not boring.
@@ -362,7 +491,8 @@
         line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
                 Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
                 0 /* ellipsisStart, 0 since text has not been ellipsized at this point */,
-                0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */);
+                0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */,
+                useFallbackLineSpacing);
         fm.width = (int) Math.ceil(line.metrics(fm));
         TextLine.recycle(line);
 
@@ -450,6 +580,11 @@
         return mEllipsizedWidth;
     }
 
+    @Override
+    public boolean isFallbackLineSpacingEnabled() {
+        return mUseFallbackLineSpacing;
+    }
+
     // Override draw so it will be faster.
     @Override
     public void draw(Canvas c, Path highlight, Paint highlightpaint,
@@ -471,6 +606,7 @@
 
     private String mDirect;
     private Paint mPaint;
+    private boolean mUseFallbackLineSpacing;
 
     /* package */ int mBottom, mDesc;   // for Direct
     private int mTopPadding, mBottomPadding;
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index 32b3bc6..2f7fb2f 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -143,9 +143,9 @@
         @Override
         public FontConfig createFromParcel(Parcel source) {
             List<FontFamily> families = source.readParcelableList(new ArrayList<>(),
-                    FontFamily.class.getClassLoader(), android.text.FontConfig.FontFamily.class);
+                    FontFamily.class.getClassLoader());
             List<Alias> aliases = source.readParcelableList(new ArrayList<>(),
-                    Alias.class.getClassLoader(), android.text.FontConfig.Alias.class);
+                    Alias.class.getClassLoader());
             long lastModifiedDate = source.readLong();
             int configVersion = source.readInt();
             return new FontConfig(families, aliases, lastModifiedDate, configVersion);
@@ -617,7 +617,7 @@
             @Override
             public FontFamily createFromParcel(Parcel source) {
                 List<Font> fonts = source.readParcelableList(
-                        new ArrayList<>(), Font.class.getClassLoader(), android.text.FontConfig.Font.class);
+                        new ArrayList<>(), Font.class.getClassLoader());
                 String name = source.readString8();
                 String langTags = source.readString8();
                 int variant = source.readInt();
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index da3e9b6..95adb77 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -591,7 +591,8 @@
             } else {
                 tl.set(paint, buf, start, end, dir, directions, hasTab, tabStops,
                         getEllipsisStart(lineNum),
-                        getEllipsisStart(lineNum) + getEllipsisCount(lineNum));
+                        getEllipsisStart(lineNum) + getEllipsisCount(lineNum),
+                        isFallbackLineSpacingEnabled());
                 if (justify) {
                     tl.justify(right - left - indentWidth);
                 }
@@ -960,6 +961,15 @@
     }
 
     /**
+     * Return true if the fallback line space is enabled in this Layout.
+     *
+     * @return true if the fallback line space is enabled. Otherwise returns false.
+     */
+    public boolean isFallbackLineSpacingEnabled() {
+        return false;
+    }
+
+    /**
      * Returns true if the character at offset and the preceding character
      * are at different run levels (and thus there's a split caret).
      * @param offset the offset
@@ -1231,7 +1241,8 @@
 
         TextLine tl = TextLine.obtain();
         tl.set(mPaint, mText, start, end, dir, directions, hasTab, tabStops,
-                getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line));
+                getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line),
+                isFallbackLineSpacingEnabled());
         float wid = tl.measure(offset - start, trailing, null);
         TextLine.recycle(tl);
 
@@ -1271,7 +1282,8 @@
 
         TextLine tl = TextLine.obtain();
         tl.set(mPaint, mText, start, end, dir, directions, hasTab, tabStops,
-                getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line));
+                getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line),
+                isFallbackLineSpacingEnabled());
         boolean[] trailings = primaryIsTrailingPreviousAllLineOffsets(line);
         if (!primary) {
             for (int offset = 0; offset < trailings.length; ++offset) {
@@ -1456,7 +1468,8 @@
         paint.setStartHyphenEdit(getStartHyphenEdit(line));
         paint.setEndHyphenEdit(getEndHyphenEdit(line));
         tl.set(paint, mText, start, end, dir, directions, hasTabs, tabStops,
-                getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line));
+                getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line),
+                isFallbackLineSpacingEnabled());
         if (isJustificationRequired(line)) {
             tl.justify(getJustifyWidth(line));
         }
@@ -1486,7 +1499,8 @@
         paint.setStartHyphenEdit(getStartHyphenEdit(line));
         paint.setEndHyphenEdit(getEndHyphenEdit(line));
         tl.set(paint, mText, start, end, dir, directions, hasTabs, tabStops,
-                getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line));
+                getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line),
+                isFallbackLineSpacingEnabled());
         if (isJustificationRequired(line)) {
             tl.justify(getJustifyWidth(line));
         }
@@ -1572,7 +1586,8 @@
         // XXX: we don't care about tabs as we just use TextLine#getOffsetToLeftRightOf here.
         tl.set(mPaint, mText, lineStartOffset, lineEndOffset, getParagraphDirection(line), dirs,
                 false, null,
-                getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line));
+                getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line),
+                isFallbackLineSpacingEnabled());
         final HorizontalMeasurementProvider horizontal =
                 new HorizontalMeasurementProvider(line, primary);
 
@@ -1828,7 +1843,8 @@
         TextLine tl = TextLine.obtain();
         // XXX: we don't care about tabs
         tl.set(mPaint, mText, lineStart, lineEnd, lineDir, directions, false, null,
-                getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line));
+                getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line),
+                isFallbackLineSpacingEnabled());
         caret = lineStart + tl.getOffsetToLeftRightOf(caret - lineStart, toLeft);
         TextLine.recycle(tl);
         return caret;
@@ -2202,7 +2218,8 @@
                 }
             }
             tl.set(paint, text, start, end, dir, directions, hasTabs, tabStops,
-                    0 /* ellipsisStart */, 0 /* ellipsisEnd */);
+                    0 /* ellipsisStart */, 0 /* ellipsisEnd */,
+                    false /* use fallback line spacing. unused */);
             return margin + Math.abs(tl.metrics(null));
         } finally {
             TextLine.recycle(tl);
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 4789231..b1bc766 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -612,7 +612,6 @@
         TextPaint paint = b.mPaint;
         int outerWidth = b.mWidth;
         TextDirectionHeuristic textDir = b.mTextDir;
-        final boolean fallbackLineSpacing = b.mFallbackLineSpacing;
         float spacingmult = b.mSpacingMult;
         float spacingadd = b.mSpacingAdd;
         float ellipsizedWidth = b.mEllipsizedWidth;
@@ -630,6 +629,7 @@
         mLineCount = 0;
         mEllipsized = false;
         mMaxLineHeight = mMaximumVisibleLineCount < 1 ? 0 : DEFAULT_MAX_LINE_HEIGHT;
+        mFallbackLineSpacing = b.mFallbackLineSpacing;
 
         int v = 0;
         boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
@@ -867,17 +867,17 @@
 
                     boolean moreChars = (endPos < bufEnd);
 
-                    final int ascent = fallbackLineSpacing
+                    final int ascent = mFallbackLineSpacing
                             ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
                             : fmAscent;
-                    final int descent = fallbackLineSpacing
+                    final int descent = mFallbackLineSpacing
                             ? Math.max(fmDescent, Math.round(descents[breakIndex]))
                             : fmDescent;
 
                     // The fallback ascent/descent may be larger than top/bottom of the default font
                     // metrics. Adjust top/bottom with ascent/descent for avoiding unexpected
                     // clipping.
-                    if (fallbackLineSpacing) {
+                    if (mFallbackLineSpacing) {
                         if (ascent < fmTop) {
                             fmTop = ascent;
                         }
@@ -1381,6 +1381,11 @@
         return mEllipsizedWidth;
     }
 
+    @Override
+    public boolean isFallbackLineSpacingEnabled() {
+        return mFallbackLineSpacing;
+    }
+
     /**
      * Return the total height of this layout.
      *
@@ -1407,6 +1412,7 @@
     @UnsupportedAppUsage
     private int mColumns;
     private int mEllipsizedWidth;
+    private boolean mFallbackLineSpacing;
 
     /**
      * Keeps track if ellipsize is applied to the text.
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 1a7ec7f..49e2111 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -71,6 +71,8 @@
     private Spanned mSpanned;
     private PrecomputedText mComputed;
 
+    private boolean mUseFallbackExtent = false;
+
     // The start and end of a potentially existing ellipsis on this text line.
     // We use them to filter out replacement and metric affecting spans on ellipsized away chars.
     private int mEllipsisStart;
@@ -141,6 +143,7 @@
         tl.mTabs = null;
         tl.mChars = null;
         tl.mComputed = null;
+        tl.mUseFallbackExtent = false;
 
         tl.mMetricAffectingSpanSpanSet.recycle();
         tl.mCharacterStyleSpanSet.recycle();
@@ -171,17 +174,20 @@
      * @param ellipsisStart the start of the ellipsis relative to the line
      * @param ellipsisEnd the end of the ellipsis relative to the line. When there
      *                    is no ellipsis, this should be equal to ellipsisStart.
+     * @param useFallbackLineSpacing true for enabling fallback line spacing. false for disabling
+     *                              fallback line spacing.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
             Directions directions, boolean hasTabs, TabStops tabStops,
-            int ellipsisStart, int ellipsisEnd) {
+            int ellipsisStart, int ellipsisEnd, boolean useFallbackLineSpacing) {
         mPaint = paint;
         mText = text;
         mStart = start;
         mLen = limit - start;
         mDir = dir;
         mDirections = directions;
+        mUseFallbackExtent = useFallbackLineSpacing;
         if (mDirections == null) {
             throw new IllegalArgumentException("Directions cannot be null");
         }
@@ -845,6 +851,30 @@
                 previousLeading);
     }
 
+    private void expandMetricsFromPaint(TextPaint wp, int start, int end,
+            int contextStart, int contextEnd, boolean runIsRtl, FontMetricsInt fmi) {
+
+        final int previousTop     = fmi.top;
+        final int previousAscent  = fmi.ascent;
+        final int previousDescent = fmi.descent;
+        final int previousBottom  = fmi.bottom;
+        final int previousLeading = fmi.leading;
+
+        int count = end - start;
+        int contextCount = contextEnd - contextStart;
+        if (mCharsValid) {
+            wp.getFontMetricsInt(mChars, start, count, contextStart, contextCount, runIsRtl,
+                    fmi);
+        } else {
+            wp.getFontMetricsInt(mText, mStart + start, count, mStart + contextStart, contextCount,
+                    runIsRtl, fmi);
+        }
+
+        updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom,
+                previousLeading);
+    }
+
+
     static void updateMetrics(FontMetricsInt fmi, int previousTop, int previousAscent,
             int previousDescent, int previousBottom, int previousLeading) {
         fmi.top     = Math.min(fmi.top,     previousTop);
@@ -949,6 +979,10 @@
             shapeTextRun(consumer, wp, start, end, contextStart, contextEnd, runIsRtl, leftX);
         }
 
+        if (mUseFallbackExtent && fmi != null) {
+            expandMetricsFromPaint(wp, start, end, contextStart, contextEnd, runIsRtl, fmi);
+        }
+
         if (c != null) {
             if (wp.bgColor != 0) {
                 int previousColor = wp.getColor();
diff --git a/core/java/android/text/TextShaper.java b/core/java/android/text/TextShaper.java
index 02fd7b4..a1d6cc8 100644
--- a/core/java/android/text/TextShaper.java
+++ b/core/java/android/text/TextShaper.java
@@ -222,7 +222,8 @@
                     mp.getDirections(0, count),
                     false /* tabstop is not supported */,
                     null,
-                    -1, -1 // ellipsis is not supported.
+                    -1, -1, // ellipsis is not supported.
+                    false /* fallback line spacing is not used */
             );
             tl.shape(consumer);
         } finally {
diff --git a/core/java/android/text/style/EasyEditSpan.java b/core/java/android/text/style/EasyEditSpan.java
index 3da8333..ccccdcf 100644
--- a/core/java/android/text/style/EasyEditSpan.java
+++ b/core/java/android/text/style/EasyEditSpan.java
@@ -82,7 +82,7 @@
      * Constructor called from {@link TextUtils} to restore the span.
      */
     public EasyEditSpan(@NonNull Parcel source) {
-        mPendingIntent = source.readParcelable(null, android.app.PendingIntent.class);
+        mPendingIntent = source.readParcelable(null);
         mDeleteEnabled = (source.readByte() == 1);
     }
 
diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java
index adb379a..2355769 100644
--- a/core/java/android/text/style/TextAppearanceSpan.java
+++ b/core/java/android/text/style/TextAppearanceSpan.java
@@ -249,7 +249,7 @@
         mTypeface = LeakyTypefaceStorage.readTypefaceFromParcel(src);
 
         mTextFontWeight = src.readInt();
-        mTextLocales = src.readParcelable(LocaleList.class.getClassLoader(), android.os.LocaleList.class);
+        mTextLocales = src.readParcelable(LocaleList.class.getClassLoader());
 
         mShadowRadius = src.readFloat();
         mShadowDx = src.readFloat();
diff --git a/core/java/android/util/MemoryIntArray.java b/core/java/android/util/MemoryIntArray.java
index 5cbbbef..42181c3 100644
--- a/core/java/android/util/MemoryIntArray.java
+++ b/core/java/android/util/MemoryIntArray.java
@@ -80,7 +80,7 @@
 
     private MemoryIntArray(Parcel parcel) throws IOException {
         mIsOwner = false;
-        ParcelFileDescriptor pfd = parcel.readParcelable(null, android.os.ParcelFileDescriptor.class);
+        ParcelFileDescriptor pfd = parcel.readParcelable(null);
         if (pfd == null) {
             throw new IOException("No backing file descriptor");
         }
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 678c80a..b8614cc 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -449,8 +449,8 @@
         type = source.readInt();
         displayId = source.readInt();
         displayGroupId = source.readInt();
-        address = source.readParcelable(null, android.view.DisplayAddress.class);
-        deviceProductInfo = source.readParcelable(null, android.hardware.display.DeviceProductInfo.class);
+        address = source.readParcelable(null);
+        deviceProductInfo = source.readParcelable(null);
         name = source.readString8();
         appWidth = source.readInt();
         appHeight = source.readInt();
@@ -475,7 +475,7 @@
         for (int i = 0; i < nColorModes; i++) {
             supportedColorModes[i] = source.readInt();
         }
-        hdrCapabilities = source.readParcelable(null, android.view.Display.HdrCapabilities.class);
+        hdrCapabilities = source.readParcelable(null);
         minimalPostProcessingSupported = source.readBoolean();
         logicalDensityDpi = source.readInt();
         physicalXDpi = source.readFloat();
diff --git a/core/java/android/view/KeyboardShortcutInfo.java b/core/java/android/view/KeyboardShortcutInfo.java
index 118b03c..2660e74 100644
--- a/core/java/android/view/KeyboardShortcutInfo.java
+++ b/core/java/android/view/KeyboardShortcutInfo.java
@@ -91,7 +91,7 @@
 
     private KeyboardShortcutInfo(Parcel source) {
         mLabel = source.readCharSequence();
-        mIcon = source.readParcelable(null, android.graphics.drawable.Icon.class);
+        mIcon = source.readParcelable(null);
         mBaseCharacter = (char) source.readInt();
         mKeycode = source.readInt();
         mModifiers = source.readInt();
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 07d5fc5..25e0eca 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -1896,7 +1896,7 @@
 
         private Canvas mCanvas;
         private Bitmap mBitmap;
-        private boolean mEnabledHwBitmapsInSwMode;
+        private boolean mEnabledHwFeaturesInSwMode;
 
         @Override
         public Canvas getCanvas(View view, int width, int height) {
@@ -1913,7 +1913,7 @@
             if (mCanvas == null) {
                 mCanvas = new Canvas();
             }
-            mEnabledHwBitmapsInSwMode = mCanvas.isHwBitmapsInSwModeEnabled();
+            mEnabledHwFeaturesInSwMode = mCanvas.isHwFeaturesInSwModeEnabled();
             mCanvas.setBitmap(mBitmap);
             return mCanvas;
         }
@@ -1921,7 +1921,7 @@
         @Override
         public Bitmap createBitmap() {
             mCanvas.setBitmap(null);
-            mCanvas.setHwBitmapsInSwModeEnabled(mEnabledHwBitmapsInSwMode);
+            mCanvas.setHwFeaturesInSwModeEnabled(mEnabledHwFeaturesInSwMode);
             return mBitmap;
         }
     }
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index a427ab8..6ad2d9a 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -1315,7 +1315,7 @@
         record.mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
         record.mBeforeText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
         record.mParcelableData = parcel.readParcelable(null);
-        parcel.readList(record.mText, null, java.lang.CharSequence.class);
+        parcel.readList(record.mText, null);
         record.mSourceWindowId = parcel.readInt();
         record.mSourceNodeId = parcel.readLong();
         record.mSourceDisplayId = parcel.readInt();
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index 540f5dc..67e6d3f 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -917,7 +917,7 @@
                 final int count = source.readInt();
                 for (int i = 0; i < count; i++) {
                     List<AccessibilityWindowInfo> windows = new ArrayList<>();
-                    source.readParcelableList(windows, loader, android.view.accessibility.AccessibilityWindowInfo.class);
+                    source.readParcelableList(windows, loader);
                     array.put(source.readInt(), windows);
                 }
                 return array;
diff --git a/core/java/android/view/autofill/ParcelableMap.java b/core/java/android/view/autofill/ParcelableMap.java
index 3fa7734..d8459aa 100644
--- a/core/java/android/view/autofill/ParcelableMap.java
+++ b/core/java/android/view/autofill/ParcelableMap.java
@@ -56,8 +56,8 @@
                     ParcelableMap map = new ParcelableMap(size);
 
                     for (int i = 0; i < size; i++) {
-                        AutofillId key = source.readParcelable(null, android.view.autofill.AutofillId.class);
-                        AutofillValue value = source.readParcelable(null, android.view.autofill.AutofillValue.class);
+                        AutofillId key = source.readParcelable(null);
+                        AutofillValue value = source.readParcelable(null);
 
                         map.put(key, value);
                     }
diff --git a/core/java/android/view/contentcapture/ContentCaptureCondition.java b/core/java/android/view/contentcapture/ContentCaptureCondition.java
index 685ea1a..027c8d2 100644
--- a/core/java/android/view/contentcapture/ContentCaptureCondition.java
+++ b/core/java/android/view/contentcapture/ContentCaptureCondition.java
@@ -133,7 +133,7 @@
 
                 @Override
                 public ContentCaptureCondition createFromParcel(@NonNull Parcel parcel) {
-                    return new ContentCaptureCondition(parcel.readParcelable(null, android.content.LocusId.class),
+                    return new ContentCaptureCondition(parcel.readParcelable(null),
                             parcel.readInt());
                 }
 
diff --git a/core/java/android/view/contentcapture/ContentCaptureContext.java b/core/java/android/view/contentcapture/ContentCaptureContext.java
index 59b5286..3bc9a96 100644
--- a/core/java/android/view/contentcapture/ContentCaptureContext.java
+++ b/core/java/android/view/contentcapture/ContentCaptureContext.java
@@ -419,7 +419,7 @@
             final ContentCaptureContext clientContext;
             if (hasClientContext) {
                 // Must reconstruct the client context using the Builder API
-                final LocusId id = parcel.readParcelable(null, android.content.LocusId.class);
+                final LocusId id = parcel.readParcelable(null);
                 final Bundle extras = parcel.readBundle();
                 final Builder builder = new Builder(id);
                 if (extras != null) builder.setExtras(extras);
@@ -427,7 +427,7 @@
             } else {
                 clientContext = null;
             }
-            final ComponentName componentName = parcel.readParcelable(null, android.content.ComponentName.class);
+            final ComponentName componentName = parcel.readParcelable(null);
             if (componentName == null) {
                 // Client-state only
                 return clientContext;
diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java
index ba4176f..0f4bc19 100644
--- a/core/java/android/view/contentcapture/ContentCaptureEvent.java
+++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java
@@ -620,7 +620,7 @@
             final int type = parcel.readInt();
             final long eventTime  = parcel.readLong();
             final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, type, eventTime);
-            final AutofillId id = parcel.readParcelable(null, android.view.autofill.AutofillId.class);
+            final AutofillId id = parcel.readParcelable(null);
             if (id != null) {
                 event.setAutofillId(id);
             }
@@ -637,13 +637,13 @@
                 event.setParentSessionId(parcel.readInt());
             }
             if (type == TYPE_SESSION_STARTED || type == TYPE_CONTEXT_UPDATED) {
-                event.setClientContext(parcel.readParcelable(null, android.view.contentcapture.ContentCaptureContext.class));
+                event.setClientContext(parcel.readParcelable(null));
             }
             if (type == TYPE_VIEW_INSETS_CHANGED) {
-                event.setInsets(parcel.readParcelable(null, android.graphics.Insets.class));
+                event.setInsets(parcel.readParcelable(null));
             }
             if (type == TYPE_WINDOW_BOUNDS_CHANGED) {
-                event.setBounds(parcel.readParcelable(null, android.graphics.Rect.class));
+                event.setBounds(parcel.readParcelable(null));
             }
             if (type == TYPE_VIEW_TEXT_CHANGED) {
                 event.setComposingIndex(parcel.readInt(), parcel.readInt());
diff --git a/core/java/android/view/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java
index 1762a58..1b4a00f 100644
--- a/core/java/android/view/contentcapture/ViewNode.java
+++ b/core/java/android/view/contentcapture/ViewNode.java
@@ -124,10 +124,10 @@
         mFlags = nodeFlags;
 
         if ((nodeFlags & FLAGS_HAS_AUTOFILL_ID) != 0) {
-            mAutofillId = parcel.readParcelable(null, android.view.autofill.AutofillId.class);
+            mAutofillId = parcel.readParcelable(null);
         }
         if ((nodeFlags & FLAGS_HAS_AUTOFILL_PARENT_ID) != 0) {
-            mParentAutofillId = parcel.readParcelable(null, android.view.autofill.AutofillId.class);
+            mParentAutofillId = parcel.readParcelable(null);
         }
         if ((nodeFlags & FLAGS_HAS_TEXT) != 0) {
             mText = new ViewNodeText(parcel, (nodeFlags & FLAGS_HAS_COMPLEX_TEXT) == 0);
@@ -169,7 +169,7 @@
             mExtras = parcel.readBundle();
         }
         if ((nodeFlags & FLAGS_HAS_LOCALE_LIST) != 0) {
-            mLocaleList = parcel.readParcelable(null, android.os.LocaleList.class);
+            mLocaleList = parcel.readParcelable(null);
         }
         if ((nodeFlags & FLAGS_HAS_MIME_TYPES) != 0) {
             mReceiveContentMimeTypes = parcel.readStringArray();
@@ -196,7 +196,7 @@
             mAutofillHints = parcel.readStringArray();
         }
         if ((nodeFlags & FLAGS_HAS_AUTOFILL_VALUE) != 0) {
-            mAutofillValue = parcel.readParcelable(null, android.view.autofill.AutofillValue.class);
+            mAutofillValue = parcel.readParcelable(null);
         }
         if ((nodeFlags & FLAGS_HAS_AUTOFILL_OPTIONS) != 0) {
             mAutofillOptions = parcel.readCharSequenceArray();
diff --git a/core/java/android/view/inputmethod/CursorAnchorInfo.java b/core/java/android/view/inputmethod/CursorAnchorInfo.java
index 437e54f..fbc9470 100644
--- a/core/java/android/view/inputmethod/CursorAnchorInfo.java
+++ b/core/java/android/view/inputmethod/CursorAnchorInfo.java
@@ -140,7 +140,7 @@
         mInsertionMarkerTop = source.readFloat();
         mInsertionMarkerBaseline = source.readFloat();
         mInsertionMarkerBottom = source.readFloat();
-        mCharacterBoundsArray = source.readParcelable(SparseRectFArray.class.getClassLoader(), android.view.inputmethod.SparseRectFArray.class);
+        mCharacterBoundsArray = source.readParcelable(SparseRectFArray.class.getClassLoader());
         mMatrixValues = source.createFloatArray();
     }
 
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index 09a1448..4cbd477 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -1067,7 +1067,7 @@
                     res.hintText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
                     res.label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
                     res.packageName = source.readString();
-                    res.autofillId = source.readParcelable(AutofillId.class.getClassLoader(), android.view.autofill.AutofillId.class);
+                    res.autofillId = source.readParcelable(AutofillId.class.getClassLoader());
                     res.fieldId = source.readInt();
                     res.fieldName = source.readString();
                     res.extras = source.readBundle();
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
index 70279cc..e1e1755 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
@@ -490,7 +490,7 @@
         boolean clientSupported = (flg & 0x200) != 0;
         int maxSuggestionCount = in.readInt();
         List<InlinePresentationSpec> inlinePresentationSpecs = new ArrayList<>();
-        in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader(), android.widget.inline.InlinePresentationSpec.class);
+        in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader());
         String hostPackageName = in.readString();
         LocaleList supportedLocales = (LocaleList) in.readTypedObject(LocaleList.CREATOR);
         Bundle extras = in.readBundle();
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsResponse.java b/core/java/android/view/inputmethod/InlineSuggestionsResponse.java
index 532fc85..b393c67 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsResponse.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsResponse.java
@@ -170,7 +170,7 @@
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
         List<InlineSuggestion> inlineSuggestions = new ArrayList<>();
-        in.readParcelableList(inlineSuggestions, InlineSuggestion.class.getClassLoader(), android.view.inputmethod.InlineSuggestion.class);
+        in.readParcelableList(inlineSuggestions, InlineSuggestion.class.getClassLoader());
 
         this.mInlineSuggestions = inlineSuggestions;
         com.android.internal.util.AnnotationValidations.validate(
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 71e7d57..fda72d5 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -396,12 +396,17 @@
      * @param requestId
      * @hide
      */
-    void canStartStylusHandwriting(int requestId);
+    default void canStartStylusHandwriting(int requestId) {
+        // intentionally empty
+    }
 
     /**
      * Start stylus handwriting session.
      * @hide
      */
-    void startStylusHandwriting(@NonNull InputChannel channel, @Nullable List<MotionEvent> events);
+    default void startStylusHandwriting(
+            @NonNull InputChannel channel, @Nullable List<MotionEvent> events) {
+        // intentionally empty
+    }
 
 }
diff --git a/core/java/android/view/textclassifier/ConversationAction.java b/core/java/android/view/textclassifier/ConversationAction.java
index a4a5a1e..bf0409d 100644
--- a/core/java/android/view/textclassifier/ConversationAction.java
+++ b/core/java/android/view/textclassifier/ConversationAction.java
@@ -141,7 +141,7 @@
 
     private ConversationAction(Parcel in) {
         mType = in.readString();
-        mAction = in.readParcelable(null, android.app.RemoteAction.class);
+        mAction = in.readParcelable(null);
         mTextReply = in.readCharSequence();
         mScore = in.readFloat();
         mExtras = in.readBundle();
diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java
index 7a6a3cd..6ad5cb9 100644
--- a/core/java/android/view/textclassifier/ConversationActions.java
+++ b/core/java/android/view/textclassifier/ConversationActions.java
@@ -149,7 +149,7 @@
         }
 
         private Message(Parcel in) {
-            mAuthor = in.readParcelable(null, android.app.Person.class);
+            mAuthor = in.readParcelable(null);
             mReferenceTime =
                     in.readInt() == 0
                             ? null
@@ -331,13 +331,13 @@
 
         private static Request readFromParcel(Parcel in) {
             List<Message> conversation = new ArrayList<>();
-            in.readParcelableList(conversation, null, android.view.textclassifier.ConversationActions.Message.class);
-            TextClassifier.EntityConfig typeConfig = in.readParcelable(null, android.view.textclassifier.TextClassifier.EntityConfig.class);
+            in.readParcelableList(conversation, null);
+            TextClassifier.EntityConfig typeConfig = in.readParcelable(null);
             int maxSuggestions = in.readInt();
             List<String> hints = new ArrayList<>();
             in.readStringList(hints);
             Bundle extras = in.readBundle();
-            SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class);
+            SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null);
 
             Request request = new Request(
                     conversation,
diff --git a/core/java/android/view/textclassifier/SelectionEvent.java b/core/java/android/view/textclassifier/SelectionEvent.java
index b347010..858825b 100644
--- a/core/java/android/view/textclassifier/SelectionEvent.java
+++ b/core/java/android/view/textclassifier/SelectionEvent.java
@@ -172,7 +172,7 @@
         mEnd = in.readInt();
         mSmartStart = in.readInt();
         mSmartEnd = in.readInt();
-        mSystemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class);
+        mSystemTcMetadata = in.readParcelable(null);
     }
 
     @Override
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 8b04d35..7db35d4 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -713,12 +713,12 @@
             final CharSequence text = in.readCharSequence();
             final int startIndex = in.readInt();
             final int endIndex = in.readInt();
-            final LocaleList defaultLocales = in.readParcelable(null, android.os.LocaleList.class);
+            final LocaleList defaultLocales = in.readParcelable(null);
             final String referenceTimeString = in.readString();
             final ZonedDateTime referenceTime = referenceTimeString == null
                     ? null : ZonedDateTime.parse(referenceTimeString);
             final Bundle extras = in.readBundle();
-            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class);
+            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null);
 
             final Request request = new Request(text, startIndex, endIndex,
                     defaultLocales, referenceTime, extras);
diff --git a/core/java/android/view/textclassifier/TextClassificationContext.java b/core/java/android/view/textclassifier/TextClassificationContext.java
index 3a50809..5d5683f 100644
--- a/core/java/android/view/textclassifier/TextClassificationContext.java
+++ b/core/java/android/view/textclassifier/TextClassificationContext.java
@@ -159,7 +159,7 @@
         mPackageName = in.readString();
         mWidgetType = in.readString();
         mWidgetVersion = in.readString();
-        mSystemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class);
+        mSystemTcMetadata = in.readParcelable(null);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<TextClassificationContext> CREATOR =
diff --git a/core/java/android/view/textclassifier/TextClassifierEvent.java b/core/java/android/view/textclassifier/TextClassifierEvent.java
index 195565c..90667cf 100644
--- a/core/java/android/view/textclassifier/TextClassifierEvent.java
+++ b/core/java/android/view/textclassifier/TextClassifierEvent.java
@@ -189,7 +189,7 @@
         mEventCategory = in.readInt();
         mEventType = in.readInt();
         mEntityTypes = in.readStringArray();
-        mEventContext = in.readParcelable(null, android.view.textclassifier.TextClassificationContext.class);
+        mEventContext = in.readParcelable(null);
         mResultId = in.readString();
         mEventIndex = in.readInt();
         int scoresLength = in.readInt();
diff --git a/core/java/android/view/textclassifier/TextLanguage.java b/core/java/android/view/textclassifier/TextLanguage.java
index 67167c6..604979b 100644
--- a/core/java/android/view/textclassifier/TextLanguage.java
+++ b/core/java/android/view/textclassifier/TextLanguage.java
@@ -295,7 +295,7 @@
         private static Request readFromParcel(Parcel in) {
             final CharSequence text = in.readCharSequence();
             final Bundle extra = in.readBundle();
-            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class);
+            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null);
 
             final Request request = new Request(text, extra);
             request.setSystemTextClassifierMetadata(systemTcMetadata);
diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java
index 445e9ec..dea3a90 100644
--- a/core/java/android/view/textclassifier/TextLinks.java
+++ b/core/java/android/view/textclassifier/TextLinks.java
@@ -558,13 +558,13 @@
 
         private static Request readFromParcel(Parcel in) {
             final String text = in.readString();
-            final LocaleList defaultLocales = in.readParcelable(null, android.os.LocaleList.class);
-            final EntityConfig entityConfig = in.readParcelable(null, android.view.textclassifier.TextClassifier.EntityConfig.class);
+            final LocaleList defaultLocales = in.readParcelable(null);
+            final EntityConfig entityConfig = in.readParcelable(null);
             final Bundle extras = in.readBundle();
             final String referenceTimeString = in.readString();
             final ZonedDateTime referenceTime = referenceTimeString == null
                     ? null : ZonedDateTime.parse(referenceTimeString);
-            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class);
+            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null);
 
             final Request request = new Request(text, defaultLocales, entityConfig,
                     /* legacyFallback= */ true, referenceTime, extras);
diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java
index dda0fcdd..c1913f6 100644
--- a/core/java/android/view/textclassifier/TextSelection.java
+++ b/core/java/android/view/textclassifier/TextSelection.java
@@ -489,9 +489,9 @@
             final CharSequence text = in.readCharSequence();
             final int startIndex = in.readInt();
             final int endIndex = in.readInt();
-            final LocaleList defaultLocales = in.readParcelable(null, android.os.LocaleList.class);
+            final LocaleList defaultLocales = in.readParcelable(null);
             final Bundle extras = in.readBundle();
-            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class);
+            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null);
             final boolean includeTextClassification = in.readBoolean();
 
             final Request request = new Request(text, startIndex, endIndex, defaultLocales,
@@ -548,6 +548,6 @@
         mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in);
         mId = in.readString();
         mExtras = in.readBundle();
-        mTextClassification = in.readParcelable(TextClassification.class.getClassLoader(), android.view.textclassifier.TextClassification.class);
+        mTextClassification = in.readParcelable(TextClassification.class.getClassLoader());
     }
 }
diff --git a/core/java/android/view/translation/TranslationRequest.java b/core/java/android/view/translation/TranslationRequest.java
index 027edc2..0d41851 100644
--- a/core/java/android/view/translation/TranslationRequest.java
+++ b/core/java/android/view/translation/TranslationRequest.java
@@ -255,9 +255,9 @@
 
         int flags = in.readInt();
         List<TranslationRequestValue> translationRequestValues = new ArrayList<>();
-        in.readParcelableList(translationRequestValues, TranslationRequestValue.class.getClassLoader(), android.view.translation.TranslationRequestValue.class);
+        in.readParcelableList(translationRequestValues, TranslationRequestValue.class.getClassLoader());
         List<ViewTranslationRequest> viewTranslationRequests = new ArrayList<>();
-        in.readParcelableList(viewTranslationRequests, ViewTranslationRequest.class.getClassLoader(), android.view.translation.ViewTranslationRequest.class);
+        in.readParcelableList(viewTranslationRequests, ViewTranslationRequest.class.getClassLoader());
 
         this.mFlags = flags;
 
diff --git a/core/java/android/view/translation/TranslationSpec.java b/core/java/android/view/translation/TranslationSpec.java
index 76dda5f..efc3d8b 100644
--- a/core/java/android/view/translation/TranslationSpec.java
+++ b/core/java/android/view/translation/TranslationSpec.java
@@ -64,7 +64,7 @@
     }
 
     static ULocale unparcelLocale(Parcel in) {
-        return (ULocale) in.readSerializable(android.icu.util.ULocale.class.getClassLoader(), android.icu.util.ULocale.class);
+        return (ULocale) in.readSerializable();
     }
 
     /**
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 60ce651..6284bc2 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -1073,7 +1073,7 @@
                 com.android.internal.R.dimen.textview_error_popup_default_width);
         final StaticLayout l = StaticLayout.Builder.obtain(text, 0, text.length(), tv.getPaint(),
                 defaultWidthInPixels)
-                .setUseLineSpacingFromFallbacks(tv.mUseFallbackLineSpacing)
+                .setUseLineSpacingFromFallbacks(tv.isFallbackLineSpacingForStaticLayout())
                 .build();
 
         float max = 0;
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index e243aae..51869d4 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -1309,7 +1309,7 @@
         private SavedState(Parcel in) {
             super(in);
             expandedGroupMetadataList = new ArrayList<ExpandableListConnector.GroupMetadata>();
-            in.readList(expandedGroupMetadataList, ExpandableListConnector.class.getClassLoader(), android.widget.ExpandableListConnector.GroupMetadata.class);
+            in.readList(expandedGroupMetadataList, ExpandableListConnector.class.getClassLoader());
         }
 
         @Override
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 6d58ee2..e60f9a6 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1489,7 +1489,7 @@
 
         SetRippleDrawableColor(Parcel parcel) {
             viewId = parcel.readInt();
-            mColorStateList = parcel.readParcelable(null, android.content.res.ColorStateList.class);
+            mColorStateList = parcel.readParcelable(null);
         }
 
         public void writeToParcel(Parcel dest, int flags) {
@@ -6628,6 +6628,7 @@
                 opts = ActivityOptions.makeBasic();
                 opts.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             }
+            opts.setLaunchDisplayId(view.getDisplay().getDisplayId());
             return Pair.create(intent, opts);
         }
     }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 67a2715..1a808b2 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -48,6 +48,9 @@
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.app.assist.AssistStructure;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ClipData;
 import android.content.ClipDescription;
@@ -453,6 +456,22 @@
 
     private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500;
 
+    /**
+     * This change ID enables the fallback text line spacing (line height) for BoringLayout.
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    public static final long BORINGLAYOUT_FALLBACK_LINESPACING = 210923482L; // buganizer id
+
+    /**
+     * This change ID enables the fallback text line spacing (line height) for StaticLayout.
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.P)
+    public static final long STATICLAYOUT_FALLBACK_LINESPACING = 37756858; // buganizer id
+
     // System wide time for last cut, copy or text changed action.
     static long sLastCutCopyOrTextChangedTime;
 
@@ -766,8 +785,13 @@
     private boolean mListenerChanged = false;
     // True if internationalized input should be used for numbers and date and time.
     private final boolean mUseInternationalizedInput;
-    // True if fallback fonts that end up getting used should be allowed to affect line spacing.
-    /* package */ boolean mUseFallbackLineSpacing;
+
+    // Fallback fonts that end up getting used should be allowed to affect line spacing.
+    private static final int FALLBACK_LINE_SPACING_NONE = 0;
+    private static final int FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY = 1;
+    private static final int FALLBACK_LINE_SPACING_ALL = 2;
+
+    private int mUseFallbackLineSpacing;
     // True if the view text can be padded for compat reasons, when the view is translated.
     private final boolean mUseTextPaddingForUiTranslation;
 
@@ -1479,7 +1503,13 @@
 
         final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
         mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O;
-        mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.P;
+        if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) {
+            mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_ALL;
+        } else if (CompatChanges.isChangeEnabled(STATICLAYOUT_FALLBACK_LINESPACING)) {
+            mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY;
+        } else {
+            mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_NONE;
+        }
         // TODO(b/179693024): Use a ChangeId instead.
         mUseTextPaddingForUiTranslation = targetSdkVersion <= Build.VERSION_CODES.R;
 
@@ -4541,8 +4571,18 @@
      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
      */
     public void setFallbackLineSpacing(boolean enabled) {
-        if (mUseFallbackLineSpacing != enabled) {
-            mUseFallbackLineSpacing = enabled;
+        int fallbackStrategy;
+        if (enabled) {
+            if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) {
+                fallbackStrategy = FALLBACK_LINE_SPACING_ALL;
+            } else {
+                fallbackStrategy = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY;
+            }
+        } else {
+            fallbackStrategy = FALLBACK_LINE_SPACING_NONE;
+        }
+        if (mUseFallbackLineSpacing != fallbackStrategy) {
+            mUseFallbackLineSpacing = fallbackStrategy;
             if (mLayout != null) {
                 nullLayouts();
                 requestLayout();
@@ -4560,7 +4600,17 @@
      */
     @InspectableProperty
     public boolean isFallbackLineSpacing() {
-        return mUseFallbackLineSpacing;
+        return mUseFallbackLineSpacing != FALLBACK_LINE_SPACING_NONE;
+    }
+
+    private boolean isFallbackLineSpacingForBoringLayout() {
+        return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL;
+    }
+
+    // Package privte for accessing from Editor.java
+    /* package */ boolean isFallbackLineSpacingForStaticLayout() {
+        return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL
+                || mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY;
     }
 
     /**
@@ -9148,7 +9198,7 @@
 
             if (hintBoring == UNKNOWN_BORING) {
                 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
-                                                   mHintBoring);
+                        isFallbackLineSpacingForBoringLayout(), mHintBoring);
                 if (hintBoring != null) {
                     mHintBoring = hintBoring;
                 }
@@ -9190,7 +9240,7 @@
                         .setTextDirection(mTextDir)
                         .setLineSpacing(mSpacingAdd, mSpacingMult)
                         .setIncludePad(mIncludePad)
-                        .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
+                        .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
                         .setBreakStrategy(mBreakStrategy)
                         .setHyphenationFrequency(mHyphenationFrequency)
                         .setJustificationMode(mJustificationMode)
@@ -9250,7 +9300,7 @@
                     .setTextDirection(mTextDir)
                     .setLineSpacing(mSpacingAdd, mSpacingMult)
                     .setIncludePad(mIncludePad)
-                    .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
+                    .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
                     .setBreakStrategy(mBreakStrategy)
                     .setHyphenationFrequency(mHyphenationFrequency)
                     .setJustificationMode(mJustificationMode)
@@ -9259,7 +9309,8 @@
             result = builder.build();
         } else {
             if (boring == UNKNOWN_BORING) {
-                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
+                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
+                        isFallbackLineSpacingForBoringLayout(), mBoring);
                 if (boring != null) {
                     mBoring = boring;
                 }
@@ -9303,7 +9354,7 @@
                     .setTextDirection(mTextDir)
                     .setLineSpacing(mSpacingAdd, mSpacingMult)
                     .setIncludePad(mIncludePad)
-                    .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
+                    .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
                     .setBreakStrategy(mBreakStrategy)
                     .setHyphenationFrequency(mHyphenationFrequency)
                     .setJustificationMode(mJustificationMode)
@@ -9430,7 +9481,8 @@
             }
 
             if (des < 0) {
-                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
+                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
+                        isFallbackLineSpacingForBoringLayout(), mBoring);
                 if (boring != null) {
                     mBoring = boring;
                 }
@@ -9463,7 +9515,8 @@
                 }
 
                 if (hintDes < 0) {
-                    hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
+                    hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
+                            isFallbackLineSpacingForBoringLayout(), mHintBoring);
                     if (hintBoring != null) {
                         mHintBoring = hintBoring;
                     }
@@ -9667,7 +9720,7 @@
         layoutBuilder.setAlignment(getLayoutAlignment())
                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
                 .setIncludePad(getIncludeFontPadding())
-                .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
+                .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout())
                 .setBreakStrategy(getBreakStrategy())
                 .setHyphenationFrequency(getHyphenationFrequency())
                 .setJustificationMode(getJustificationMode())
diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java
index 974a1dd..88ece5c 100644
--- a/core/java/android/window/DisplayAreaOrganizer.java
+++ b/core/java/android/window/DisplayAreaOrganizer.java
@@ -101,14 +101,6 @@
     public static final int FEATURE_IME_PLACEHOLDER = FEATURE_SYSTEM_FIRST + 7;
 
     /**
-     * Display area for one handed background layer, which preventing when user
-     * turning the Dark theme on, they can not clearly identify the screen has entered
-     * one handed mode.
-     * @hide
-     */
-    public static final int FEATURE_ONE_HANDED_BACKGROUND_PANEL = FEATURE_SYSTEM_FIRST + 8;
-
-    /**
      * Display area hosting IME window tokens (@see ImeContainer). By default, IMEs are parented
      * to FEATURE_IME_PLACEHOLDER but can be reparented under other RootDisplayArea.
      *
@@ -118,7 +110,7 @@
      * app on another screen).
      * @hide
      */
-    public static final int FEATURE_IME = FEATURE_SYSTEM_FIRST + 9;
+    public static final int FEATURE_IME = FEATURE_SYSTEM_FIRST + 8;
 
     /**
      * The last boundary of display area for system features
diff --git a/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java b/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java
index d709acf..6f83bf3 100644
--- a/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java
+++ b/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java
@@ -89,6 +89,6 @@
 
     public void readFromParcel(Parcel source) {
         mSdp = source.readString();
-        mCapInfo = source.readParcelable(CapInfo.class.getClassLoader(), com.android.ims.internal.uce.common.CapInfo.class);
+        mCapInfo = source.readParcelable(CapInfo.class.getClassLoader());
     }
 }
\ No newline at end of file
diff --git a/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java b/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java
index 559d61b..461f8bf 100644
--- a/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java
+++ b/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java
@@ -147,8 +147,8 @@
     /** @hide */
     public void readFromParcel(Parcel source) {
         mUserData = source.readInt();
-        mCmdId = source.readParcelable(OptionsCmdId.class.getClassLoader(), com.android.ims.internal.uce.options.OptionsCmdId.class);
-        mStatus = source.readParcelable(StatusCode.class.getClassLoader(), com.android.ims.internal.uce.common.StatusCode.class);
-        mCapInfo = source.readParcelable(CapInfo.class.getClassLoader(), com.android.ims.internal.uce.common.CapInfo.class);
+        mCmdId = source.readParcelable(OptionsCmdId.class.getClassLoader());
+        mStatus = source.readParcelable(StatusCode.class.getClassLoader());
+        mCapInfo = source.readParcelable(CapInfo.class.getClassLoader());
     }
 }
\ No newline at end of file
diff --git a/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java b/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java
index 160f9eb..3242081 100644
--- a/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java
+++ b/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java
@@ -180,7 +180,7 @@
         mRequestId = source.readInt();
         mSipResponseCode = source.readInt();
         mReasonPhrase = source.readString();
-        mCmdId = source.readParcelable(OptionsCmdId.class.getClassLoader(), com.android.ims.internal.uce.options.OptionsCmdId.class);
+        mCmdId = source.readParcelable(OptionsCmdId.class.getClassLoader());
         mRetryAfter = source.readInt();
         mReasonHeader = source.readString();
     }
diff --git a/core/java/com/android/ims/internal/uce/presence/PresCapInfo.java b/core/java/com/android/ims/internal/uce/presence/PresCapInfo.java
index f0ee5f3..ec8b6bf 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresCapInfo.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresCapInfo.java
@@ -105,6 +105,6 @@
     /** @hide */
     public void readFromParcel(Parcel source) {
         mContactUri = source.readString();
-        mCapInfo = source.readParcelable(CapInfo.class.getClassLoader(), com.android.ims.internal.uce.common.CapInfo.class);
+        mCapInfo = source.readParcelable(CapInfo.class.getClassLoader());
     }
 }
diff --git a/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java b/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java
index 8fbb000c..7e22106 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java
@@ -146,8 +146,8 @@
     public void readFromParcel(Parcel source) {
         mUserData = source.readInt();
         mRequestId = source.readInt();
-        mCmdId = source.readParcelable(PresCmdId.class.getClassLoader(), com.android.ims.internal.uce.presence.PresCmdId.class);
-        mStatus = source.readParcelable(StatusCode.class.getClassLoader(), com.android.ims.internal.uce.common.StatusCode.class);
+        mCmdId = source.readParcelable(PresCmdId.class.getClassLoader());
+        mStatus = source.readParcelable(StatusCode.class.getClassLoader());
     }
 
 }
\ No newline at end of file
diff --git a/core/java/com/android/ims/internal/uce/presence/PresResInfo.java b/core/java/com/android/ims/internal/uce/presence/PresResInfo.java
index 954c2b6..2f797b4 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresResInfo.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresResInfo.java
@@ -122,6 +122,6 @@
     public void readFromParcel(Parcel source) {
         mResUri = source.readString();
         mDisplayName = source.readString();
-        mInstanceInfo = source.readParcelable(PresResInstanceInfo.class.getClassLoader(), com.android.ims.internal.uce.presence.PresResInstanceInfo.class);
+        mInstanceInfo = source.readParcelable(PresResInstanceInfo.class.getClassLoader());
     }
 }
\ No newline at end of file
diff --git a/core/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.java b/core/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.java
index 733c0af..0130ef4 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.java
@@ -190,7 +190,7 @@
         mResInstanceState = source.readInt();
         mPresentityUri = source.readString();
         Parcelable[] tempParcelableArray = source.readParcelableArray(
-                                    PresTupleInfo.class.getClassLoader());
+                                    PresTupleInfo.class.getClassLoader(), PresTupleInfo.class);
         mTupleInfoArray = new PresTupleInfo[] {};
         if(tempParcelableArray != null) {
             mTupleInfoArray = Arrays.copyOf(tempParcelableArray, tempParcelableArray.length,
diff --git a/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java b/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java
index 63247db..e33aa13 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java
@@ -236,7 +236,7 @@
         mListName = source.readString();
         mRequestId = source.readInt();
         mPresSubscriptionState = source.readParcelable(
-                                  PresSubscriptionState.class.getClassLoader(), com.android.ims.internal.uce.presence.PresSubscriptionState.class);
+                                  PresSubscriptionState.class.getClassLoader());
         mSubscriptionExpireTime = source.readInt();
         mSubscriptionTerminatedReason = source.readString();
     }
diff --git a/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java b/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java
index 8097a37..5e394ef 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java
@@ -185,7 +185,7 @@
         mRequestId = source.readInt();
         mSipResponseCode = source.readInt();
         mReasonPhrase = source.readString();
-        mCmdId = source.readParcelable(PresCmdId.class.getClassLoader(), com.android.ims.internal.uce.presence.PresCmdId.class);
+        mCmdId = source.readParcelable(PresCmdId.class.getClassLoader());
         mRetryAfter = source.readInt();
         mReasonHeader = source.readString();
     }
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 025f711..be7388b 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -759,11 +759,11 @@
         }
 
         try {
-            IBinder permissionToken = ActivityTaskManager.getService()
-                    .requestStartActivityPermissionToken(getActivityToken());
             Intent delegationIntent = new Intent();
             final ComponentName delegateActivity = ComponentName.unflattenFromString(
                     Resources.getSystem().getString(R.string.config_chooserActivity));
+            IBinder permissionToken = ActivityTaskManager.getService()
+                    .requestStartActivityPermissionToken(delegateActivity);
             delegationIntent.setComponent(delegateActivity);
             delegationIntent.putExtra(Intent.EXTRA_INTENT, getIntent());
             delegationIntent.putExtra(ActivityTaskManager.EXTRA_PERMISSION_TOKEN, permissionToken);
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index b273f6d..f9a8c7b 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1437,11 +1437,11 @@
         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.
-            IBinder permissionToken = ActivityTaskManager.getService()
-                    .requestStartActivityPermissionToken(getActivityToken());
             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);
diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
index 289daee..9c3c224 100644
--- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
+++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
@@ -237,11 +237,12 @@
     private DisplayResolveInfo(Parcel in) {
         mDisplayLabel = in.readCharSequence();
         mExtendedInfo = in.readCharSequence();
-        mResolvedIntent = in.readParcelable(null /* ClassLoader */, android.content.Intent.class);
+        mResolvedIntent = in.readParcelable(null /* ClassLoader */);
         mSourceIntents.addAll(
-                Arrays.asList((Intent[]) in.readParcelableArray(null /* ClassLoader */)));
+                Arrays.asList((Intent[]) in.readParcelableArray(null /* ClassLoader */,
+                        Intent.class)));
         mIsSuspended = in.readBoolean();
         mPinned = in.readBoolean();
-        mResolveInfo = in.readParcelable(null /* ClassLoader */, android.content.pm.ResolveInfo.class);
+        mResolveInfo = in.readParcelable(null /* ClassLoader */);
     }
 }
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index f904610..13a39de 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -523,6 +523,12 @@
      */
     public static final String TASK_MANAGER_ENABLED = "task_manager_enabled";
 
+
+    /**
+     * (boolean) Whether the clipboard overlay is enabled.
+     */
+    public static final String CLIPBOARD_OVERLAY_ENABLED = "clipboard_overlay_enabled";
+
     private SystemUiDeviceConfigFlags() {
     }
 }
diff --git a/core/java/com/android/internal/infra/AndroidFuture.java b/core/java/com/android/internal/infra/AndroidFuture.java
index 84391c1..0443ad0 100644
--- a/core/java/com/android/internal/infra/AndroidFuture.java
+++ b/core/java/com/android/internal/infra/AndroidFuture.java
@@ -24,7 +24,6 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
-import android.util.EventLog;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
@@ -585,6 +584,7 @@
     /**
      * @see #writeThrowable
      */
+    @SuppressWarnings("UnsafeParcelApi")
     private static @Nullable Throwable readThrowable(@NonNull Parcel parcel) {
         final boolean hasThrowable = parcel.readBoolean();
         if (!hasThrowable) {
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index d14054d..e1a67d8 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -24,8 +24,6 @@
 import static android.view.SurfaceControl.JankData.PREDICTION_ERROR;
 import static android.view.SurfaceControl.JankData.SURFACE_FLINGER_SCHEDULING;
 
-import static com.android.internal.jank.InteractionJankMonitor.ACTION_METRICS_LOGGED;
-import static com.android.internal.jank.InteractionJankMonitor.ACTION_SESSION_BEGIN;
 import static com.android.internal.jank.InteractionJankMonitor.ACTION_SESSION_CANCEL;
 import static com.android.internal.jank.InteractionJankMonitor.ACTION_SESSION_END;
 
@@ -241,7 +239,6 @@
             if (!mSurfaceOnly) {
                 mRendererWrapper.addObserver(mObserver);
             }
-            notifyCujEvent(ACTION_SESSION_BEGIN);
         }
     }
 
@@ -523,7 +520,6 @@
                     maxFrameTimeNanos, /* will be 0 if mSurfaceOnly == true */
                     missedSfFramesCount,
                     missedAppFramesCount);
-            notifyCujEvent(ACTION_METRICS_LOGGED);
         }
         if (DEBUG) {
             Log.i(TAG, "finish: CUJ=" + mSession.getName()
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index a33b2f1..cd5d0a1 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -16,10 +16,7 @@
 
 package com.android.internal.jank;
 
-import static android.content.Intent.FLAG_RECEIVER_REGISTERED_ONLY;
-
 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL;
-import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NOT_BEGUN;
 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
 import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
 import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN;
@@ -71,11 +68,9 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.content.Context;
-import android.content.Intent;
 import android.os.Build;
 import android.os.HandlerExecutor;
 import android.os.HandlerThread;
-import android.os.SystemProperties;
 import android.provider.DeviceConfig;
 import android.text.TextUtils;
 import android.util.Log;
@@ -131,14 +126,8 @@
     private static final int DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES = 3;
     private static final int DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS = 64;
 
-    public static final String ACTION_SESSION_BEGIN = ACTION_PREFIX + ".ACTION_SESSION_BEGIN";
     public static final String ACTION_SESSION_END = ACTION_PREFIX + ".ACTION_SESSION_END";
     public static final String ACTION_SESSION_CANCEL = ACTION_PREFIX + ".ACTION_SESSION_CANCEL";
-    public static final String ACTION_METRICS_LOGGED = ACTION_PREFIX + ".ACTION_METRICS_LOGGED";
-    public static final String BUNDLE_KEY_CUJ_NAME = ACTION_PREFIX + ".CUJ_NAME";
-    public static final String BUNDLE_KEY_TIMESTAMP = ACTION_PREFIX + ".TIMESTAMP";
-    @VisibleForTesting
-    public static final String PROP_NOTIFY_CUJ_EVENT = "debug.jank.notify_cuj_events";
 
     // Every value must have a corresponding entry in CUJ_STATSD_INTERACTION_TYPE.
     public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = 0;
@@ -374,8 +363,7 @@
                 new ChoreographerWrapper(Choreographer.getInstance());
 
         synchronized (mLock) {
-            FrameTrackerListener eventsListener =
-                    (s, act) -> handleCujEvents(config.getContext(), act, s);
+            FrameTrackerListener eventsListener = (s, act) -> handleCujEvents(act, s);
             return new FrameTracker(session, mWorker.getThreadHandler(),
                     threadedRenderer, viewRoot, surfaceControl, choreographer, mMetrics,
                     mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis,
@@ -383,24 +371,13 @@
         }
     }
 
-    private void handleCujEvents(Context context, String action, Session session) {
+    private void handleCujEvents(String action, Session session) {
         // Clear the running and timeout tasks if the end / cancel was fired within the tracker.
         // Or we might have memory leaks.
         if (needRemoveTasks(action, session)) {
             removeTimeout(session.getCuj());
             removeTracker(session.getCuj());
         }
-
-        // Notify the receivers if necessary.
-        if (session.shouldNotify()) {
-            if (context != null) {
-                notifyEvents(context, action, session);
-            } else {
-                throw new IllegalArgumentException(
-                        "Can't notify cuj events due to lack of context: cuj="
-                        + session.getName() + ", action=" + action);
-            }
-        }
     }
 
     private boolean needRemoveTasks(String action, Session session) {
@@ -412,22 +389,6 @@
         return badEnd || badCancel;
     }
 
-    /**
-     * Notifies who may interest in some CUJ events.
-     */
-    @VisibleForTesting
-    public void notifyEvents(Context context, String action, Session session) {
-        if (action.equals(ACTION_SESSION_CANCEL)
-                && session.getReason() == REASON_CANCEL_NOT_BEGUN) {
-            return;
-        }
-        Intent intent = new Intent(action);
-        intent.putExtra(BUNDLE_KEY_CUJ_NAME, getNameOfCuj(session.getCuj()));
-        intent.putExtra(BUNDLE_KEY_TIMESTAMP, session.getTimeStamp());
-        intent.addFlags(FLAG_RECEIVER_REGISTERED_ONLY);
-        context.sendBroadcast(intent);
-    }
-
     private void removeTimeout(@CujType int cujType) {
         synchronized (mLock) {
             Runnable timeout = mTimeoutActions.get(cujType);
@@ -625,7 +586,17 @@
      */
     public static String getNameOfInteraction(int interactionType) {
         // There is an offset amount of 1 between cujType and interactionType.
-        return getNameOfCuj(interactionType - 1);
+        return getNameOfCuj(getCujTypeFromInteraction(interactionType));
+    }
+
+    /**
+     * A helper method to translate interaction type to CUJ type.
+     *
+     * @param interactionType the interaction type defined in AtomsProto.java
+     * @return the integer in {@link CujType}
+     */
+    private static int getCujTypeFromInteraction(int interactionType) {
+        return interactionType - 1;
     }
 
     /**
@@ -935,13 +906,11 @@
         private final long mTimeStamp;
         @Reasons
         private int mReason = REASON_END_UNKNOWN;
-        private final boolean mShouldNotify;
         private final String mName;
 
         public Session(@CujType int cujType, @NonNull String postfix) {
             mCujType = cujType;
             mTimeStamp = System.nanoTime();
-            mShouldNotify = SystemProperties.getBoolean(PROP_NOTIFY_CUJ_EVENT, false);
             mName = TextUtils.isEmpty(postfix)
                     ? String.format("J<%s>", getNameOfCuj(mCujType))
                     : String.format("J<%s::%s>", getNameOfCuj(mCujType), postfix);
@@ -981,10 +950,5 @@
         public @Reasons int getReason() {
             return mReason;
         }
-
-        /** Determines if should notify the receivers of cuj events */
-        public boolean shouldNotify() {
-            return mShouldNotify;
-        }
     }
 }
diff --git a/core/java/com/android/internal/net/LegacyVpnInfo.java b/core/java/com/android/internal/net/LegacyVpnInfo.java
index b3bc93a..43984b5 100644
--- a/core/java/com/android/internal/net/LegacyVpnInfo.java
+++ b/core/java/com/android/internal/net/LegacyVpnInfo.java
@@ -69,7 +69,7 @@
             LegacyVpnInfo info = new LegacyVpnInfo();
             info.key = in.readString();
             info.state = in.readInt();
-            info.intent = in.readParcelable(null, android.app.PendingIntent.class);
+            info.intent = in.readParcelable(null);
             return info;
         }
 
diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java
index 2a203ac..2ae56f8 100644
--- a/core/java/com/android/internal/net/VpnConfig.java
+++ b/core/java/com/android/internal/net/VpnConfig.java
@@ -34,8 +34,6 @@
 import android.os.Parcelable;
 import android.os.UserHandle;
 
-import java.net.Inet4Address;
-import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -93,8 +91,8 @@
     public String interfaze;
     public String session;
     public int mtu = -1;
-    public List<LinkAddress> addresses = new ArrayList<LinkAddress>();
-    public List<RouteInfo> routes = new ArrayList<RouteInfo>();
+    public List<LinkAddress> addresses = new ArrayList<>();
+    public List<RouteInfo> routes = new ArrayList<>();
     public List<String> dnsServers;
     public List<String> searchDomains;
     public List<String> allowedApplications;
@@ -114,12 +112,32 @@
     public VpnConfig() {
     }
 
-    public void updateAllowedFamilies(InetAddress address) {
-        if (address instanceof Inet4Address) {
-            allowIPv4 = true;
-        } else {
-            allowIPv6 = true;
-        }
+    public VpnConfig(VpnConfig other) {
+        user = other.user;
+        interfaze = other.interfaze;
+        session = other.session;
+        mtu = other.mtu;
+        addresses = copyOf(other.addresses);
+        routes = copyOf(other.routes);
+        dnsServers = copyOf(other.dnsServers);
+        searchDomains = copyOf(other.searchDomains);
+        allowedApplications = copyOf(other.allowedApplications);
+        disallowedApplications = copyOf(other.disallowedApplications);
+        configureIntent = other.configureIntent;
+        startTime = other.startTime;
+        legacy = other.legacy;
+        blocking = other.blocking;
+        allowBypass = other.allowBypass;
+        allowIPv4 = other.allowIPv4;
+        allowIPv6 = other.allowIPv6;
+        isMetered = other.isMetered;
+        underlyingNetworks = other.underlyingNetworks != null ? Arrays.copyOf(
+                other.underlyingNetworks, other.underlyingNetworks.length) : null;
+        proxyInfo = other.proxyInfo;
+    }
+
+    private static <T> List<T> copyOf(List<T> list) {
+        return list != null ? new ArrayList<>(list) : null;
     }
 
     public void addLegacyRoutes(String routesStr) {
@@ -131,7 +149,6 @@
             //each route is ip/prefix
             RouteInfo info = new RouteInfo(new IpPrefix(route), null, null, RouteInfo.RTN_UNICAST);
             this.routes.add(info);
-            updateAllowedFamilies(info.getDestination().getAddress());
         }
     }
 
@@ -144,7 +161,6 @@
             //each address is ip/prefix
             LinkAddress addr = new LinkAddress(address);
             this.addresses.add(addr);
-            updateAllowedFamilies(addr.getAddress());
         }
     }
 
@@ -192,7 +208,7 @@
             config.searchDomains = in.createStringArrayList();
             config.allowedApplications = in.createStringArrayList();
             config.disallowedApplications = in.createStringArrayList();
-            config.configureIntent = in.readParcelable(null, android.app.PendingIntent.class);
+            config.configureIntent = in.readParcelable(null);
             config.startTime = in.readLong();
             config.legacy = in.readInt() != 0;
             config.blocking = in.readInt() != 0;
@@ -201,7 +217,7 @@
             config.allowIPv6 = in.readInt() != 0;
             config.isMetered = in.readInt() != 0;
             config.underlyingNetworks = in.createTypedArray(Network.CREATOR);
-            config.proxyInfo = in.readParcelable(null, android.net.ProxyInfo.class);
+            config.proxyInfo = in.readParcelable(null);
             return config;
         }
 
diff --git a/core/java/com/android/internal/net/VpnProfile.java b/core/java/com/android/internal/net/VpnProfile.java
index 519faa8..d8dc143 100644
--- a/core/java/com/android/internal/net/VpnProfile.java
+++ b/core/java/com/android/internal/net/VpnProfile.java
@@ -182,9 +182,9 @@
         ipsecCaCert = in.readString();
         ipsecServerCert = in.readString();
         saveLogin = in.readInt() != 0;
-        proxy = in.readParcelable(null, android.net.ProxyInfo.class);
+        proxy = in.readParcelable(null);
         mAllowedAlgorithms = new ArrayList<>();
-        in.readList(mAllowedAlgorithms, null, java.lang.String.class);
+        in.readList(mAllowedAlgorithms, null);
         isBypassable = in.readBoolean();
         isMetered = in.readBoolean();
         maxMtu = in.readInt();
diff --git a/core/java/com/android/internal/os/AppFuseMount.java b/core/java/com/android/internal/os/AppFuseMount.java
index 5404fea..04d7211 100644
--- a/core/java/com/android/internal/os/AppFuseMount.java
+++ b/core/java/com/android/internal/os/AppFuseMount.java
@@ -57,7 +57,7 @@
             new Parcelable.Creator<AppFuseMount>() {
         @Override
         public AppFuseMount createFromParcel(Parcel in) {
-            return new AppFuseMount(in.readInt(), in.readParcelable(null, android.os.ParcelFileDescriptor.class));
+            return new AppFuseMount(in.readInt(), in.readParcelable(null));
         }
 
         @Override
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 21f719c..9429c79 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -26,6 +26,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.usage.NetworkStatsManager;
 import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.bluetooth.UidTraffic;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -37,7 +38,6 @@
 import android.database.ContentObserver;
 import android.hardware.usb.UsbManager;
 import android.location.GnssSignalQuality;
-import android.net.INetworkStatsService;
 import android.net.NetworkStats;
 import android.net.Uri;
 import android.net.wifi.WifiManager;
@@ -137,7 +137,9 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Queue;
+import java.util.Set;
 import java.util.concurrent.Future;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.ReentrantLock;
@@ -12534,19 +12536,11 @@
     private NetworkStats mLastModemNetworkStats = new NetworkStats(0, -1);
 
     @VisibleForTesting
-    protected NetworkStats readNetworkStatsLocked(String[] ifaces) {
-        try {
-            if (!ArrayUtils.isEmpty(ifaces)) {
-                INetworkStatsService statsService = INetworkStatsService.Stub.asInterface(
-                        ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
-                if (statsService != null) {
-                    return statsService.getDetailedUidStats(ifaces);
-                } else {
-                    Slog.e(TAG, "Failed to get networkStatsService ");
-                }
-            }
-        } catch (RemoteException e) {
-            Slog.e(TAG, "failed to read network stats for ifaces: " + Arrays.toString(ifaces) + e);
+    protected NetworkStats readNetworkStatsLocked(@NonNull NetworkStatsManager networkStatsManager,
+            String[] ifaces) {
+        Objects.requireNonNull(networkStatsManager);
+        if (!ArrayUtils.isEmpty(ifaces)) {
+            return networkStatsManager.getDetailedUidStats(Set.of(ifaces));
         }
         return null;
     }
@@ -12557,7 +12551,8 @@
      */
     @GuardedBy("this")
     public void updateWifiState(@Nullable final WifiActivityEnergyInfo info,
-            final long consumedChargeUC, long elapsedRealtimeMs, long uptimeMs) {
+            final long consumedChargeUC, long elapsedRealtimeMs, long uptimeMs,
+            @NonNull NetworkStatsManager networkStatsManager) {
         if (DEBUG_ENERGY) {
             synchronized (mWifiNetworkLock) {
                 Slog.d(TAG, "Updating wifi stats: " + Arrays.toString(mWifiIfaces));
@@ -12567,7 +12562,8 @@
         // Grab a separate lock to acquire the network stats, which may do I/O.
         NetworkStats delta = null;
         synchronized (mWifiNetworkLock) {
-            final NetworkStats latestStats = readNetworkStatsLocked(mWifiIfaces);
+            final NetworkStats latestStats = readNetworkStatsLocked(networkStatsManager,
+                    mWifiIfaces);
             if (latestStats != null) {
                 delta = NetworkStats.subtract(latestStats, mLastWifiNetworkStats, null, null,
                         mNetworkStatsPool.acquire());
@@ -12920,7 +12916,8 @@
      * Distribute Cell radio energy info and network traffic to apps.
      */
     public void noteModemControllerActivity(@Nullable final ModemActivityInfo activityInfo,
-            final long consumedChargeUC, long elapsedRealtimeMs, long uptimeMs) {
+            final long consumedChargeUC, long elapsedRealtimeMs, long uptimeMs,
+            @NonNull NetworkStatsManager networkStatsManager) {
         if (DEBUG_ENERGY) {
             Slog.d(TAG, "Updating mobile radio stats with " + activityInfo);
         }
@@ -12934,7 +12931,8 @@
         // Grab a separate lock to acquire the network stats, which may do I/O.
         NetworkStats delta = null;
         synchronized (mModemNetworkLock) {
-            final NetworkStats latestStats = readNetworkStatsLocked(mModemIfaces);
+            final NetworkStats latestStats = readNetworkStatsLocked(networkStatsManager,
+                    mModemIfaces);
             if (latestStats != null) {
                 delta = NetworkStats.subtract(latestStats, mLastModemNetworkStats, null, null,
                         mNetworkStatsPool.acquire());
diff --git a/core/java/com/android/internal/statusbar/StatusBarIcon.java b/core/java/com/android/internal/statusbar/StatusBarIcon.java
index 4f80afa..1d62623 100644
--- a/core/java/com/android/internal/statusbar/StatusBarIcon.java
+++ b/core/java/com/android/internal/statusbar/StatusBarIcon.java
@@ -81,9 +81,9 @@
     }
 
     public void readFromParcel(Parcel in) {
-        this.icon = (Icon) in.readParcelable(null, android.graphics.drawable.Icon.class);
+        this.icon = (Icon) in.readParcelable(null);
         this.pkg = in.readString();
-        this.user = (UserHandle) in.readParcelable(null, android.os.UserHandle.class);
+        this.user = (UserHandle) in.readParcelable(null);
         this.iconLevel = in.readInt();
         this.visible = in.readInt() != 0;
         this.number = in.readInt();
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index d3c3917..f46223a 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -71,11 +71,11 @@
 
             if (in.readInt() == 1) {
                 mBitmapBundle = in.readBundle(getClass().getClassLoader());
-                mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader(), android.graphics.Rect.class);
-                mInsets = in.readParcelable(Insets.class.getClassLoader(), android.graphics.Insets.class);
+                mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader());
+                mInsets = in.readParcelable(Insets.class.getClassLoader());
                 mTaskId = in.readInt();
                 mUserId = in.readInt();
-                mTopComponent = in.readParcelable(ComponentName.class.getClassLoader(), android.content.ComponentName.class);
+                mTopComponent = in.readParcelable(ComponentName.class.getClassLoader());
             }
         }
 
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index d16d9c6..db4bc2c 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -24,6 +24,8 @@
 import android.security.keystore.recovery.KeyChainProtectionParams;
 import android.security.keystore.recovery.RecoveryCertPath;
 import com.android.internal.widget.ICheckCredentialProgressCallback;
+import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
+import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.VerifyCredentialResponse;
 
@@ -96,4 +98,10 @@
     boolean tryUnlockWithCachedUnifiedChallenge(int userId);
     void removeCachedUnifiedChallenge(int userId);
     void updateEncryptionPassword(int type, in byte[] password);
+    boolean registerWeakEscrowTokenRemovedListener(in IWeakEscrowTokenRemovedListener listener);
+    boolean unregisterWeakEscrowTokenRemovedListener(in IWeakEscrowTokenRemovedListener listener);
+    long addWeakEscrowToken(in byte[] token, int userId, in IWeakEscrowTokenActivatedListener callback);
+    boolean removeWeakEscrowToken(long handle, int userId);
+    boolean isWeakEscrowTokenActive(long handle, int userId);
+    boolean isWeakEscrowTokenValid(long handle, in byte[] token, int userId);
 }
diff --git a/core/java/com/android/internal/widget/IWeakEscrowTokenActivatedListener.aidl b/core/java/com/android/internal/widget/IWeakEscrowTokenActivatedListener.aidl
new file mode 100644
index 0000000..9c8d9d6
--- /dev/null
+++ b/core/java/com/android/internal/widget/IWeakEscrowTokenActivatedListener.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.internal.widget;
+
+/** @hide */
+oneway interface IWeakEscrowTokenActivatedListener {
+    void onWeakEscrowTokenActivated(long handle, int userId);
+}
diff --git a/core/java/com/android/internal/widget/IWeakEscrowTokenRemovedListener.aidl b/core/java/com/android/internal/widget/IWeakEscrowTokenRemovedListener.aidl
new file mode 100644
index 0000000..7018048
--- /dev/null
+++ b/core/java/com/android/internal/widget/IWeakEscrowTokenRemovedListener.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.internal.widget;
+
+/** @hide */
+oneway interface IWeakEscrowTokenRemovedListener {
+    void onWeakEscrowTokenRemoved(long handle, int userId);
+}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 5a03277..f91776e 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1236,6 +1236,28 @@
         }
     }
 
+    /** Register the given WeakEscrowTokenRemovedListener. */
+    public boolean registerWeakEscrowTokenRemovedListener(
+            @NonNull final IWeakEscrowTokenRemovedListener listener) {
+        try {
+            return getLockSettings().registerWeakEscrowTokenRemovedListener(listener);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not register WeakEscrowTokenRemovedListener.");
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Unregister the given WeakEscrowTokenRemovedListener. */
+    public boolean unregisterWeakEscrowTokenRemovedListener(
+            @NonNull final IWeakEscrowTokenRemovedListener listener) {
+        try {
+            return getLockSettings().unregisterWeakEscrowTokenRemovedListener(listener);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not register WeakEscrowTokenRemovedListener.");
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     public void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId) {
         try {
             getLockSettings().reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
@@ -1355,15 +1377,38 @@
     }
 
     /**
+     * Create a weak escrow token for the current user, which can later be used to unlock FBE
+     * or change user password.
+     *
+     * After adding, if the user currently has lockscreen password, they will need to perform a
+     * confirm credential operation in order to activate the token for future use. If the user
+     * has no secure lockscreen, then the token is activated immediately.
+     *
+     * If the user changes or removes lockscreen password, activated weak escrow tokens will be
+     * removed.
+     *
+     * @return a unique 64-bit token handle which is needed to refer to this token later.
+     */
+    public long addWeakEscrowToken(byte[] token, int userId,
+            @NonNull IWeakEscrowTokenActivatedListener callback) {
+        try {
+            return getLockSettings().addWeakEscrowToken(token, userId, callback);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not add weak token.");
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Callback interface to notify when an added escrow token has been activated.
      */
     public interface EscrowTokenStateChangeCallback {
         /**
          * The method to be called when the token is activated.
          * @param handle 64 bit handle corresponding to the escrow token
-         * @param userid user for whom the escrow token has been added
+         * @param userId user for whom the escrow token has been added
          */
-        void onEscrowTokenActivated(long handle, int userid);
+        void onEscrowTokenActivated(long handle, int userId);
     }
 
     /**
@@ -1379,6 +1424,21 @@
     }
 
     /**
+     * Remove a weak escrow token.
+     *
+     * @return true if the given handle refers to a valid weak token previously returned from
+     * {@link #addWeakEscrowToken}, whether it's active or not. return false otherwise.
+     */
+    public boolean removeWeakEscrowToken(long handle, int userId) {
+        try {
+            return getLockSettings().removeWeakEscrowToken(handle, userId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not remove the weak token.");
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Check if the given escrow token is active or not. Only active token can be used to call
      * {@link #setLockCredentialWithToken} and {@link #unlockUserWithToken}
      *
@@ -1389,6 +1449,29 @@
     }
 
     /**
+     * Check if the given weak escrow token is active or not. Only active token can be used to call
+     * {@link #setLockCredentialWithToken} and {@link #unlockUserWithToken}
+     */
+    public boolean isWeakEscrowTokenActive(long handle, int userId) {
+        try {
+            return getLockSettings().isWeakEscrowTokenActive(handle, userId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not check the weak token.");
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Check if the given weak escrow token is valid. */
+    public boolean isWeakEscrowTokenValid(long handle, byte[] token, int userId) {
+        try {
+            return getLockSettings().isWeakEscrowTokenValid(handle, token, userId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not validate the weak token.");
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Change a user's lock credential with a pre-configured escrow token.
      *
      * <p>This method is only available to code running in the system server process itself.
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index 627381c..09ff4e0 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -37,6 +37,7 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.MotionEvent.PointerCoords;
+import android.view.RoundedCorner;
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -229,13 +230,29 @@
 
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        if (insets.getDisplayCutout() != null) {
-            mHeaderPaddingTop = insets.getDisplayCutout().getSafeInsetTop();
-            mWaterfallInsets = insets.getDisplayCutout().getWaterfallInsets();
-        } else {
-            mHeaderPaddingTop = 0;
-            mWaterfallInsets = Insets.NONE;
+        int headerPaddingTop = 0;
+        Insets waterfallInsets = Insets.NONE;
+
+        final RoundedCorner topLeftRounded =
+                insets.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT);
+        if (topLeftRounded != null) {
+            headerPaddingTop = topLeftRounded.getRadius();
         }
+
+        final RoundedCorner topRightRounded =
+                insets.getRoundedCorner(RoundedCorner.POSITION_TOP_RIGHT);
+        if (topRightRounded != null) {
+            headerPaddingTop = Math.max(headerPaddingTop, topRightRounded.getRadius());
+        }
+
+        if (insets.getDisplayCutout() != null) {
+            headerPaddingTop =
+                    Math.max(headerPaddingTop, insets.getDisplayCutout().getSafeInsetTop());
+            waterfallInsets = insets.getDisplayCutout().getWaterfallInsets();
+        }
+
+        mHeaderPaddingTop = headerPaddingTop;
+        mWaterfallInsets = waterfallInsets;
         return super.onApplyWindowInsets(insets);
     }
 
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index da62863..a3ac472 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -251,6 +251,7 @@
                 "spatializer-aidl-cpp",
                 "av-types-aidl-cpp",
                 "android.hardware.camera.device@3.2",
+                "libandroid_net",
                 "libandroidicu",
                 "libbattery",
                 "libbpf_android",
diff --git a/core/jni/android_server_NetworkManagementSocketTagger.cpp b/core/jni/android_server_NetworkManagementSocketTagger.cpp
index afad08a..1be1873 100644
--- a/core/jni/android_server_NetworkManagementSocketTagger.cpp
+++ b/core/jni/android_server_NetworkManagementSocketTagger.cpp
@@ -15,24 +15,23 @@
  */
 
 #define LOG_TAG "NMST_QTagUidNative"
-#include <utils/Log.h>
 
-#include <nativehelper/JNIPlatformHelp.h>
-
-#include "jni.h"
-#include <utils/misc.h>
+#include <android/multinetwork.h>
 #include <cutils/qtaguid.h>
-
 #include <errno.h>
 #include <fcntl.h>
-#include <sys/types.h>
+#include <nativehelper/JNIPlatformHelp.h>
 #include <sys/socket.h>
+#include <sys/types.h>
+#include <utils/Log.h>
+#include <utils/misc.h>
+
+#include "jni.h"
 
 namespace android {
 
-static jint QTagUid_tagSocketFd(JNIEnv* env, jclass,
-                                jobject fileDescriptor,
-                                jint tagNum, jint uid) {
+static jint tagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor,
+                        jint tagNum, jint uid) {
   int userFd = jniGetFDFromFileDescriptor(env, fileDescriptor);
 
   if (env->ExceptionCheck()) {
@@ -40,15 +39,14 @@
     return (jint)-1;
   }
 
-  int res = qtaguid_tagSocket(userFd, tagNum, uid);
+  int res = android_tag_socket_with_uid(userFd, tagNum, uid);
   if (res < 0) {
     return (jint)-errno;
   }
   return (jint)res;
 }
 
-static jint QTagUid_untagSocketFd(JNIEnv* env, jclass,
-                                  jobject fileDescriptor) {
+static jint untagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor) {
   int userFd = jniGetFDFromFileDescriptor(env, fileDescriptor);
 
   if (env->ExceptionCheck()) {
@@ -56,16 +54,14 @@
     return (jint)-1;
   }
 
-  int res = qtaguid_untagSocket(userFd);
+  int res = android_untag_socket(userFd);
   if (res < 0) {
     return (jint)-errno;
   }
   return (jint)res;
 }
 
-static jint QTagUid_setCounterSet(JNIEnv* env, jclass,
-                                  jint setNum, jint uid) {
-
+static jint setCounterSet(JNIEnv* env, jclass, jint setNum, jint uid) {
   int res = qtaguid_setCounterSet(setNum, uid);
   if (res < 0) {
     return (jint)-errno;
@@ -73,9 +69,7 @@
   return (jint)res;
 }
 
-static jint QTagUid_deleteTagData(JNIEnv* env, jclass,
-                                  jint tagNum, jint uid) {
-
+static jint deleteTagData(JNIEnv* env, jclass, jint tagNum, jint uid) {
   int res = qtaguid_deleteTagData(tagNum, uid);
   if (res < 0) {
     return (jint)-errno;
@@ -84,10 +78,10 @@
 }
 
 static const JNINativeMethod gQTagUidMethods[] = {
-  { "native_tagSocketFd", "(Ljava/io/FileDescriptor;II)I", (void*)QTagUid_tagSocketFd},
-  { "native_untagSocketFd", "(Ljava/io/FileDescriptor;)I", (void*)QTagUid_untagSocketFd},
-  { "native_setCounterSet", "(II)I", (void*)QTagUid_setCounterSet},
-  { "native_deleteTagData", "(II)I", (void*)QTagUid_deleteTagData},
+  { "native_tagSocketFd", "(Ljava/io/FileDescriptor;II)I", (void*)tagSocketFd},
+  { "native_untagSocketFd", "(Ljava/io/FileDescriptor;)I", (void*)untagSocketFd},
+  { "native_setCounterSet", "(II)I", (void*)setCounterSet},
+  { "native_deleteTagData", "(II)I", (void*)deleteTagData},
 };
 
 int register_android_server_NetworkManagementSocketTagger(JNIEnv* env) {
diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp
index 011e051..3651dbd 100644
--- a/core/jni/android_text_Hyphenator.cpp
+++ b/core/jni/android_text_Hyphenator.cpp
@@ -130,6 +130,7 @@
     addHyphenator("sk", 2, 2);  // Slovak
     addHyphenator("sl", 2, 2);  // Slovenian
     addHyphenator("sq", 2, 2);  // Albanian
+    addHyphenator("sv", 1, 2);  // Swedish
     addHyphenator("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Tamil
     addHyphenator("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Telugu
     addHyphenator("tk", 2, 2);  // Turkmen
diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto
index 73d6a17..e56d55e 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -212,6 +212,12 @@
         // DatabaseHelper.
         optional SettingProto in_silent = 3 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto when_ringing = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
+        optional SettingProto alarm_intensity = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto media_intensity = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto ring_intensity = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        // notification_intensity is already logged at Notification.vibration_intensity
+        // haptic_feedback_intensity is already logged at HapticFeedback.intensity
     }
     optional Vibrate vibrate = 32;
 
diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto
index 7b97524d..fbe2170 100644
--- a/core/proto/android/server/vibrator/vibratormanagerservice.proto
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -97,7 +97,7 @@
     optional int32 status = 6;
 }
 
-// Next id: 18
+// Next id: 24
 message VibratorManagerServiceDumpProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
     repeated int32 vibrator_ids = 1;
@@ -106,8 +106,14 @@
     optional VibrationProto current_external_vibration = 4;
     optional bool vibrator_under_external_control = 5;
     optional bool low_power_mode = 6;
+    optional int32 alarm_intensity = 18;
+    optional int32 alarm_default_intensity = 19;
     optional int32 haptic_feedback_intensity = 7;
     optional int32 haptic_feedback_default_intensity = 8;
+    optional int32 hardware_feedback_intensity = 22;
+    optional int32 hardware_feedback_default_intensity = 23;
+    optional int32 media_intensity = 20;
+    optional int32 media_default_intensity = 21;
     optional int32 notification_intensity = 9;
     optional int32 notification_default_intensity = 10;
     optional int32 ring_intensity = 11;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index dfcfd5f..777ddaa 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2182,8 +2182,9 @@
     <permission android:name="android.permission.NFC_HANDOVER_STATUS"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @hide Allows internal management of Bluetooth state when on wireless consent mode.
-         <p>Not for use by third-party applications. -->
+    <!-- @SystemApi Allows internal management of Bluetooth state when on wireless consent mode.
+         <p>Not for use by third-party applications.
+         @hide -->
     <permission android:name="android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED"
         android:protectionLevel="signature" />
 
@@ -2791,6 +2792,10 @@
     <permission android:name="android.permission.INTERACT_ACROSS_PROFILES"
         android:protectionLevel="signature|appop" />
 
+    <!-- @SystemApi @hide Allows starting activities across profiles in the same profile group. -->
+    <permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES"
+                android:protectionLevel="signature|role" />
+
     <!-- @SystemApi Allows configuring apps to have the INTERACT_ACROSS_PROFILES permission so that
          they can interact across profiles in the same profile group.
          @hide -->
@@ -4438,6 +4443,12 @@
     <permission android:name="android.permission.REVOKE_RUNTIME_PERMISSIONS"
          android:protectionLevel="signature|installer|verifier" />
 
+   <!-- @TestApi Allows an application to revoke the POST_NOTIFICATIONS permission from an app
+        without killing the app. Only granted to the shell.
+        @hide -->
+    <permission android:name="android.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL"
+        android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows the system to read runtime permission state.
         @hide -->
     <permission android:name="android.permission.GET_RUNTIME_PERMISSIONS"
@@ -5323,6 +5334,12 @@
     <permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows an application to manage weak escrow token on the device. This permission
+         is not available to third party applications.
+         @hide -->
+    <permission android:name="android.permission.MANAGE_WEAK_ESCROW_TOKEN"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Allows an application to listen to trust changes.  Only allowed for system processes.
         @hide -->
     <permission android:name="android.permission.TRUST_LISTENER"
@@ -6094,7 +6111,7 @@
          @hide -->
     <permission android:name="android.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES"
                 android:protectionLevel="signature|role" />
-    
+
     <!-- @SystemApi Allows an app to read whether SafetyCenter is enabled/disabled.
              <p>Protection level: signature|privileged
              @hide
@@ -6102,6 +6119,14 @@
     <permission android:name="android.permission.READ_SAFETY_CENTER_STATUS"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Required to access the safety center internal APIs using the
+             {@link android.safetycenter.SafetyCenterManager}.
+             <p>Protection level: internal|installer|role
+             @hide
+        -->
+    <permission android:name="android.permission.MANAGE_SAFETY_CENTER"
+                android:protectionLevel="internal|installer|role" />
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
@@ -6507,6 +6532,11 @@
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
+        <service android:name="com.android.server.SmartStorageMaintIdler"
+                android:exported="true"
+                android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
+
         <service android:name="com.android.server.ZramWriteback"
                  android:exported="false"
                  android:permission="android.permission.BIND_JOB_SERVICE" >
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 7d8bcea..f4b7b73 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1179,10 +1179,18 @@
     <string-array translatable="false" name="config_ringtoneEffectUris">
     </string-array>
 
+    <!-- The default intensity level for alarm vibrations. See
+         Settings.System.ALARM_VIBRATION_INTENSITY more details on the constant values and
+         meanings. -->
+    <integer name="config_defaultAlarmVibrationIntensity">2</integer>
     <!-- The default intensity level for haptic feedback. See
          Settings.System.HAPTIC_FEEDBACK_INTENSITY more details on the constant values and
          meanings. -->
     <integer name="config_defaultHapticFeedbackIntensity">2</integer>
+    <!-- The default intensity level for media vibrations. See
+         Settings.System.MEDIA_VIBRATION_INTENSITY more details on the constant values and
+         meanings. -->
+    <integer name="config_defaultMediaVibrationIntensity">2</integer>
     <!-- The default intensity level for notification vibrations. See
          Settings.System.NOTIFICATION_VIBRATION_INTENSITY more details on the constant values and
          meanings. -->
@@ -2125,6 +2133,8 @@
     <string name="config_deviceManager" translatable="false"></string>
     <!-- The name of the package that will hold the app protection service role. -->
     <string name="config_systemAppProtectionService" translatable="false"></string>
+    <!-- The name of the package that will hold the system calendar sync manager role. -->
+    <string name="config_systemAutomotiveCalendarSyncManager" translatable="false"></string>
 
     <!-- The name of the package that will be allowed to change its components' label/icon. -->
     <string name="config_overrideComponentUiPackage" translatable="false">com.android.stk</string>
@@ -2685,10 +2695,27 @@
     <!-- Configure mobile tcp buffer sizes in the form:
          rat-name:rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max
          If no value is found for the rat-name in use, the system default will be applied.
+
+         This is deprecated. Please use config_tcp_buffers.
     -->
     <string-array name="config_mobile_tcp_buffers">
     </string-array>
 
+    <!-- Configure tcp buffer sizes in the form:
+         rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max
+         If this is configured as an empty string, the system default will be applied.
+
+         For now this config is used by mobile data only. In the future it should be
+         used by Wi-Fi as well.
+
+         Note that starting from Android 13, the TCP buffer size is fixed after boot up, and should
+         never be changed based on carriers or the network types. The value should be configured
+         appropriately based on the device's memory and performance. It is recommended to use lower
+         values if the device has low memory or doesn't support high-speed network such like LTE,
+         NR, or Wifi.
+    -->
+    <string name="config_tcp_buffers" translatable="false"></string>
+
     <!-- Configure ethernet tcp buffersizes in the form:
          rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max -->
     <string name="config_ethernet_tcp_buffers" translatable="false">524288,1048576,3145728,524288,1048576,2097152</string>
@@ -2773,6 +2800,16 @@
         <item>350</item>
     </integer-array>
 
+    <!-- A vibration waveform for notifications that specify DEFAULT_VIBRATE.
+         This value is a float array with values grouped as
+         { targetAmplitude (within [0,1]), targetFrequency (in hertz), duration (in milliseconds) }
+         This is only applied on devices with vibration frequency control. If the device doesn't
+         support frequency control, then the vibration specified in
+         config_defaultNotificationVibePattern is used instead.
+     -->
+    <array name="config_defaultNotificationVibeWaveform">
+    </array>
+
     <!-- Vibrator pattern to be used as the default for notifications
          that do not specify vibration but vibrate anyway because the device
          is in vibrate mode.
@@ -2784,6 +2821,16 @@
         <item>100</item>
     </integer-array>
 
+    <!-- A vibration waveform for notifications that do not specify vibration but vibrate anyway,
+         because the device is in vibrate mode. This value is a float array with values grouped as
+         { targetAmplitude (within [0,1]), targetFrequency (in hertz), duration (in milliseconds) }
+         This is only applied on devices with vibration frequency control. If the device doesn't
+         support frequency control, then the vibration specified in
+         config_notificationFallbackVibePattern is used instead.
+     -->
+    <array name="config_notificationFallbackVibeWaveform">
+    </array>
+
     <!-- Flag indicating if the speed up audio on mt call code should be executed -->
     <bool name="config_speed_up_audio_on_mt_calls">false</bool>
 
@@ -5557,4 +5604,7 @@
 
     <!-- Determines whether SafetyCenter feature is enabled. -->
     <bool name="config_enableSafetyCenter">true</bool>
+
+    <!-- Flag indicating if help links for Settings app should be enabled. -->
+    <bool name="config_settingsHelpLinksEnabled">false</bool>
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 3bd8ec3..5fa7409 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3274,6 +3274,8 @@
     <public name="config_deviceManager" />
     <!-- @hide @SystemApi -->
     <public name="config_systemAppProtectionService" />
+    <!-- @hide @SystemApi @TestApi -->
+    <public name="config_systemAutomotiveCalendarSyncManager" />
   </staging-public-group>
 
   <staging-public-group type="dimen" first-id="0x01db0000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ba4aa81..0acd4bf 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -481,6 +481,7 @@
   <java-symbol type="integer" name="config_safe_media_volume_usb_mB" />
   <java-symbol type="integer" name="config_mobile_mtu" />
   <java-symbol type="array"   name="config_mobile_tcp_buffers" />
+  <java-symbol type="string"  name="config_tcp_buffers" />
   <java-symbol type="integer" name="config_volte_replacement_rat"/>
   <java-symbol type="integer" name="config_valid_wappush_index" />
   <java-symbol type="integer" name="config_overrideHasPermanentMenuKey" />
@@ -1935,7 +1936,9 @@
   <java-symbol type="array" name="config_locationExtraPackageNames" />
   <java-symbol type="array" name="config_testLocationProviders" />
   <java-symbol type="array" name="config_defaultNotificationVibePattern" />
+  <java-symbol type="array" name="config_defaultNotificationVibeWaveform" />
   <java-symbol type="array" name="config_notificationFallbackVibePattern" />
+  <java-symbol type="array" name="config_notificationFallbackVibeWaveform" />
   <java-symbol type="bool" name="config_enableServerNotificationEffectsForAutomotive" />
   <java-symbol type="bool" name="config_useAttentionLight" />
   <java-symbol type="bool" name="config_adaptive_sleep_available" />
@@ -2338,6 +2341,7 @@
   <java-symbol type="string" name="nas_upgrade_notification_disable_action" />
   <java-symbol type="string" name="nas_upgrade_notification_learn_more_action" />
   <java-symbol type="string" name="nas_upgrade_notification_learn_more_content" />
+  <java-symbol type="bool" name="config_settingsHelpLinksEnabled" />
 
   <!-- ImfTest -->
   <java-symbol type="layout" name="auto_complete_list" />
@@ -3867,7 +3871,9 @@
   <java-symbol type="drawable" name="ic_arrow_forward" />
   <java-symbol type="drawable" name="ic_permission" />
 
+  <java-symbol type="integer" name="config_defaultAlarmVibrationIntensity" />
   <java-symbol type="integer" name="config_defaultHapticFeedbackIntensity" />
+  <java-symbol type="integer" name="config_defaultMediaVibrationIntensity" />
   <java-symbol type="integer" name="config_defaultNotificationVibrationIntensity" />
   <java-symbol type="integer" name="config_defaultRingVibrationIntensity" />
 
diff --git a/core/tests/coretests/src/android/os/VibratorTest.java b/core/tests/coretests/src/android/os/VibratorTest.java
index bdd76a5..981086d 100644
--- a/core/tests/coretests/src/android/os/VibratorTest.java
+++ b/core/tests/coretests/src/android/os/VibratorTest.java
@@ -27,14 +27,22 @@
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
 import android.hardware.vibrator.IVibrator;
 import android.media.AudioAttributes;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -50,11 +58,19 @@
 @RunWith(MockitoJUnitRunner.class)
 public class VibratorTest {
 
+    @Rule
+    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
+    private Context mContextSpy;
     private Vibrator mVibratorSpy;
 
     @Before
     public void setUp() {
-        mVibratorSpy = spy(InstrumentationRegistry.getContext().getSystemService(Vibrator.class));
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+
+        ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
+        when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
+        mVibratorSpy = spy(new SystemVibrator(mContextSpy));
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/text/TextLineTest.java b/core/tests/coretests/src/android/text/TextLineTest.java
index 90ce305..412d6ec 100644
--- a/core/tests/coretests/src/android/text/TextLineTest.java
+++ b/core/tests/coretests/src/android/text/TextLineTest.java
@@ -48,7 +48,7 @@
         final TextLine tl = TextLine.obtain();
         tl.set(paint, line, 0, line.length(), Layout.DIR_LEFT_TO_RIGHT,
                 Layout.DIRS_ALL_LEFT_TO_RIGHT, false /* hasTabs */, null /* tabStops */,
-                0, 0 /* no ellipsis */);
+                0, 0 /* no ellipsis */, false /* useFallbackLinespace */);
         final float originalWidth = tl.metrics(null);
         final float expandedWidth = 2 * originalWidth;
 
@@ -105,7 +105,7 @@
         tl.set(paint, str, 0, str.length(),
                 TextDirectionHeuristics.FIRSTSTRONG_LTR.isRtl(str, 0, str.length()) ? -1 : 1,
                 layout.getLineDirections(0), tabStops != null, tabStops,
-                0, 0 /* no ellipsis */);
+                0, 0 /* no ellipsis */, false /* useFallbackLineSpacing */);
         return tl;
     }
 
@@ -276,7 +276,8 @@
 
         final TextLine tl = TextLine.obtain();
         tl.set(new TextPaint(), text, 0, text.length(), 1, Layout.DIRS_ALL_LEFT_TO_RIGHT,
-                false /* hasTabs */, null /* tabStops */, 9, 12);
+                false /* hasTabs */, null /* tabStops */, 9, 12,
+                false /* useFallbackLineSpacing */);
         tl.measure(text.length(), false /* trailing */, null /* fmi */);
 
         assertFalse(span.mIsUsed);
@@ -292,7 +293,8 @@
 
         final TextLine tl = TextLine.obtain();
         tl.set(new TextPaint(), text, 0, text.length(), 1, Layout.DIRS_ALL_LEFT_TO_RIGHT,
-                false /* hasTabs */, null /* tabStops */, 9, 12);
+                false /* hasTabs */, null /* tabStops */, 9, 12,
+                false /* useFallbackLineSpacing */);
         tl.measure(text.length(), false /* trailing */, null /* fmi */);
 
         assertTrue(span.mIsUsed);
@@ -308,7 +310,8 @@
 
         final TextLine tl = TextLine.obtain();
         tl.set(new TextPaint(), text, 0, text.length(), 1, Layout.DIRS_ALL_LEFT_TO_RIGHT,
-                false /* hasTabs */, null /* tabStops */, 9, 12);
+                false /* hasTabs */, null /* tabStops */, 9, 12,
+                false /* useFallbackLineSpacing */);
         tl.measure(text.length(), false /* trailing */, null /* fmi */);
         assertTrue(span.mIsUsed);
     }
diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
index 0d2d047..a409129 100644
--- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
@@ -176,7 +176,6 @@
     private InteractionJankMonitor createMockedInteractionJankMonitor() {
         InteractionJankMonitor monitor = spy(new InteractionJankMonitor(mWorker));
         doReturn(true).when(monitor).shouldMonitor(anyInt());
-        doNothing().when(monitor).notifyEvents(any(), any(), any());
         return monitor;
     }
 
diff --git a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
index 8d9d79d..ce2f764 100644
--- a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.app.usage.NetworkStatsManager;
 import android.net.NetworkCapabilities;
 import android.net.NetworkStats;
 import android.os.BatteryConsumer;
@@ -44,6 +45,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -51,6 +53,8 @@
 public class MobileRadioPowerCalculatorTest {
     private static final double PRECISION = 0.00001;
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
+    @Mock
+    NetworkStatsManager mNetworkStatsManager;
 
     @Rule
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
@@ -95,7 +99,8 @@
 
         ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
                 new int[]{100, 200, 300, 400, 500}, 600);
-        stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000);
+        stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000,
+                mNetworkStatsManager);
 
         mStatsRule.setTime(12_000_000, 12_000_000);
 
@@ -157,7 +162,8 @@
         mStatsRule.setNetworkStats(new NetworkStats(10000, 1)
                 .insertEntry("cellular", APP_UID, 0, 0, 1000, 100, 2000, 20, 100));
 
-        stats.noteModemControllerActivity(null, POWER_DATA_UNAVAILABLE, 10000, 10000);
+        stats.noteModemControllerActivity(null, POWER_DATA_UNAVAILABLE, 10000, 10000,
+                mNetworkStatsManager);
 
         uid.setProcessStateForTest(
                 BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 11000);
@@ -165,7 +171,8 @@
         mStatsRule.setNetworkStats(new NetworkStats(12000, 1)
                 .insertEntry("cellular", APP_UID, 0, 0, 1000, 250, 2000, 80, 200));
 
-        stats.noteModemControllerActivity(null, POWER_DATA_UNAVAILABLE, 12000, 12000);
+        stats.noteModemControllerActivity(null, POWER_DATA_UNAVAILABLE, 12000, 12000,
+                mNetworkStatsManager);
 
         assertThat(uid.getMobileRadioMeasuredBatteryConsumptionUC()).isAtMost(0);
         // 12000-8000 = 4000 ms == 4_000_000 us
@@ -239,7 +246,7 @@
 
         ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
                 new int[]{100, 200, 300, 400, 500}, 600);
-        stats.noteModemControllerActivity(mai, 10_000_000, 10000, 10000);
+        stats.noteModemControllerActivity(mai, 10_000_000, 10000, 10000, mNetworkStatsManager);
 
         mStatsRule.setTime(12_000_000, 12_000_000);
 
@@ -301,7 +308,7 @@
         mStatsRule.setNetworkStats(new NetworkStats(10000, 1)
                 .insertEntry("cellular", APP_UID, 0, 0, 1000, 100, 2000, 20, 100));
 
-        stats.noteModemControllerActivity(null, 10_000_000, 10000, 10000);
+        stats.noteModemControllerActivity(null, 10_000_000, 10000, 10000, mNetworkStatsManager);
 
         uid.setProcessStateForTest(
                 BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 11000);
@@ -309,7 +316,7 @@
         mStatsRule.setNetworkStats(new NetworkStats(12000, 1)
                 .insertEntry("cellular", APP_UID, 0, 0, 1000, 250, 2000, 80, 200));
 
-        stats.noteModemControllerActivity(null, 15_000_000, 12000, 12000);
+        stats.noteModemControllerActivity(null, 15_000_000, 12000, 12000, mNetworkStatsManager);
 
         mStatsRule.setTime(20000, 20000);
 
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index 4faf349..bddb3a1 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -20,6 +20,8 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.annotation.NonNull;
+import android.app.usage.NetworkStatsManager;
 import android.net.NetworkStats;
 import android.os.Handler;
 import android.os.Looper;
@@ -116,7 +118,8 @@
     }
 
     @Override
-    protected NetworkStats readNetworkStatsLocked(String[] ifaces) {
+    protected NetworkStats readNetworkStatsLocked(@NonNull NetworkStatsManager networkStatsManager,
+            String[] ifaces) {
         return mNetworkStats;
     }
 
diff --git a/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java
index fc44ddc..e7ce9a0 100644
--- a/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java
@@ -21,6 +21,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.app.usage.NetworkStatsManager;
 import android.net.NetworkCapabilities;
 import android.net.NetworkStats;
 import android.os.BatteryConsumer;
@@ -35,6 +36,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -43,6 +45,9 @@
 
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
 
+    @Mock
+    NetworkStatsManager mNetworkStatsManager;
+
     @Rule
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
             .setAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE, 360.0)
@@ -80,7 +85,8 @@
         final BatteryStatsImpl batteryStats = setupTestNetworkNumbers();
         final WifiActivityEnergyInfo energyInfo = setupPowerControllerBasedModelEnergyNumbersInfo();
 
-        batteryStats.updateWifiState(energyInfo, POWER_DATA_UNAVAILABLE, 1000, 1000);
+        batteryStats.updateWifiState(energyInfo, POWER_DATA_UNAVAILABLE, 1000, 1000,
+                mNetworkStatsManager);
 
         WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile());
         mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
@@ -113,7 +119,7 @@
         final BatteryStatsImpl batteryStats = setupTestNetworkNumbers();
         final WifiActivityEnergyInfo energyInfo = setupPowerControllerBasedModelEnergyNumbersInfo();
 
-        batteryStats.updateWifiState(energyInfo, 1_000_000, 1000, 1000);
+        batteryStats.updateWifiState(energyInfo, 1_000_000, 1000, 1000, mNetworkStatsManager);
 
         WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile());
         mStatsRule.apply(calculator);
@@ -160,7 +166,8 @@
 
         // Don't pass WifiActivityEnergyInfo, making WifiPowerCalculator rely exclusively
         // on the packet counts.
-        batteryStats.updateWifiState(/* energyInfo */ null, POWER_DATA_UNAVAILABLE, 1000, 1000);
+        batteryStats.updateWifiState(/* energyInfo */ null, POWER_DATA_UNAVAILABLE, 1000, 1000,
+                mNetworkStatsManager);
 
         WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile());
         mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
@@ -180,7 +187,8 @@
 
         // Don't pass WifiActivityEnergyInfo, making WifiPowerCalculator rely exclusively
         // on the packet counts.
-        batteryStats.updateWifiState(/* energyInfo */ null, 1_000_000, 1000, 1000);
+        batteryStats.updateWifiState(/* energyInfo */ null, 1_000_000, 1000, 1000,
+                mNetworkStatsManager);
 
         WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile());
         mStatsRule.apply(calculator);
diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
index 50e8474..b659f37 100644
--- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
@@ -21,13 +21,16 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.pm.UserInfo;
+import android.os.RemoteException;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.test.mock.MockContentResolver;
@@ -38,12 +41,16 @@
 
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.widget.ILockSettings;
+import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
+import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
 import com.android.internal.widget.LockPatternUtils;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 
+import java.nio.charset.StandardCharsets;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class LockPatternUtilsTest {
@@ -102,4 +109,84 @@
         configureTest(false, true, 0);
         assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
     }
+
+    @Test
+    public void testAddWeakEscrowToken() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
+        int testUserId = 10;
+        IWeakEscrowTokenActivatedListener listener = createWeakEscrowTokenListener();
+        mLockPatternUtils.addWeakEscrowToken(testToken, testUserId, listener);
+        verify(ils).addWeakEscrowToken(eq(testToken), eq(testUserId), eq(listener));
+    }
+
+    @Test
+    public void testRegisterWeakEscrowTokenRemovedListener() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
+        mLockPatternUtils.registerWeakEscrowTokenRemovedListener(testListener);
+        verify(ils).registerWeakEscrowTokenRemovedListener(eq(testListener));
+    }
+
+    @Test
+    public void testUnregisterWeakEscrowTokenRemovedListener() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
+        mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(testListener);
+        verify(ils).unregisterWeakEscrowTokenRemovedListener(eq(testListener));
+    }
+
+    @Test
+    public void testRemoveAutoEscrowToken() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        int testUserId = 10;
+        long testHandle = 100L;
+        mLockPatternUtils.removeWeakEscrowToken(testHandle, testUserId);
+        verify(ils).removeWeakEscrowToken(eq(testHandle), eq(testUserId));
+    }
+
+    @Test
+    public void testIsAutoEscrowTokenActive() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        int testUserId = 10;
+        long testHandle = 100L;
+        mLockPatternUtils.isWeakEscrowTokenActive(testHandle, testUserId);
+        verify(ils).isWeakEscrowTokenActive(eq(testHandle), eq(testUserId));
+    }
+
+    @Test
+    public void testIsAutoEscrowTokenValid() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        int testUserId = 10;
+        byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
+        long testHandle = 100L;
+        mLockPatternUtils.isWeakEscrowTokenValid(testHandle, testToken, testUserId);
+        verify(ils).isWeakEscrowTokenValid(eq(testHandle), eq(testToken), eq(testUserId));
+    }
+
+    private ILockSettings createTestLockSettings() {
+        final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+        mLockPatternUtils = spy(new LockPatternUtils(context));
+        final ILockSettings ils = Mockito.mock(ILockSettings.class);
+        when(mLockPatternUtils.getLockSettings()).thenReturn(ils);
+        return ils;
+    }
+
+    private IWeakEscrowTokenActivatedListener createWeakEscrowTokenListener() {
+        return new IWeakEscrowTokenActivatedListener.Stub() {
+            @Override
+            public void onWeakEscrowTokenActivated(long handle, int userId) {
+                // Do nothing.
+            }
+        };
+    }
+
+    private IWeakEscrowTokenRemovedListener createTestAutoEscrowTokenRemovedListener() {
+        return new IWeakEscrowTokenRemovedListener.Stub() {
+            @Override
+            public void onWeakEscrowTokenRemoved(long handle, int userId) {
+                // Do nothing.
+            }
+        };
+    }
 }
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 13303a3..ee0fb44 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -275,6 +275,7 @@
     <privapp-permissions package="com.android.server.telecom">
         <permission name="android.permission.BIND_CONNECTION_SERVICE"/>
         <permission name="android.permission.BIND_INCALL_SERVICE"/>
+        <permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
         <permission name="android.permission.CALL_PRIVILEGED"/>
         <permission name="android.permission.HANDLE_CAR_MODE_CHANGES"/>
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
@@ -293,6 +294,7 @@
 
     <privapp-permissions package="com.android.shell">
         <!-- Needed for test only -->
+        <permission name="android.permission.LAUNCH_DEVICE_MANAGER_SETUP"/>
         <permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
         <permission name="android.permission.ACCESS_LOWPAN_STATE"/>
         <permission name="android.permission.BACKUP"/>
@@ -518,9 +520,8 @@
         <permission name="android.permission.MANAGE_VOICE_KEYPHRASES" />
         <!-- Permission required for ATS test - CarDevicePolicyManagerTest -->
         <permission name="android.permission.LOCK_DEVICE" />
-        <!-- Permission required for CTS test - CtsSafetyCenterTestCases -->
+        <!-- Permissions required for CTS test - CtsSafetyCenterTestCases -->
         <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
-        <!-- Permission required for CTS test - CtsSafetyCenterTestCases -->
         <permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
     </privapp-permissions>
 
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 9584994..535d656 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -3691,6 +3691,18 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1874559932": {
+      "message": "The TaskDisplayArea with %s does not exist.",
+      "level": "WARN",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/DisplayAreaPolicyBuilder.java"
+    },
+    "1884961873": {
+      "message": "Sleep still need to stop %d activities",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/Task.java"
+    },
     "1891501279": {
       "message": "cancelAnimation(): reason=%s",
       "level": "DEBUG",
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index a612265..425a378 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -67,7 +67,7 @@
      * @hide
      */
     protected int mDensity = Bitmap.DENSITY_NONE;
-    private boolean mAllowHwBitmapsInSwMode = false;
+    private boolean mAllowHwFeaturesInSwMode = false;
 
     protected void throwIfCannotDraw(Bitmap bitmap) {
         if (bitmap.isRecycled()) {
@@ -101,14 +101,14 @@
 
     public void drawArc(float left, float top, float right, float bottom, float startAngle,
             float sweepAngle, boolean useCenter, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawArc(mNativeCanvasWrapper, left, top, right, bottom, startAngle, sweepAngle,
                 useCenter, paint.getNativeInstance());
     }
 
     public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,
             @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         drawArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, useCenter,
                 paint);
     }
@@ -119,14 +119,14 @@
 
     public void drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint) {
         throwIfCannotDraw(bitmap);
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawBitmap(mNativeCanvasWrapper, bitmap.getNativeInstance(), left, top,
                 paint != null ? paint.getNativeInstance() : 0, mDensity, mScreenDensity,
                 bitmap.mDensity);
     }
 
     public void drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix, @Nullable Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawBitmapMatrix(mNativeCanvasWrapper, bitmap.getNativeInstance(), matrix.ni(),
                 paint != null ? paint.getNativeInstance() : 0);
     }
@@ -137,7 +137,7 @@
             throw new NullPointerException();
         }
         throwIfCannotDraw(bitmap);
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
 
         int left, top, right, bottom;
@@ -163,7 +163,7 @@
             throw new NullPointerException();
         }
         throwIfCannotDraw(bitmap);
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
 
         float left, top, right, bottom;
@@ -202,7 +202,7 @@
                 || (lastScanline + width > length)) {
             throw new ArrayIndexOutOfBoundsException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         // quick escape if there's nothing to draw
         if (width == 0 || height == 0) {
             return;
@@ -226,7 +226,7 @@
         if ((meshWidth | meshHeight | vertOffset | colorOffset) < 0) {
             throw new ArrayIndexOutOfBoundsException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         if (meshWidth == 0 || meshHeight == 0) {
             return;
         }
@@ -243,7 +243,7 @@
     }
 
     public void drawCircle(float cx, float cy, float radius, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawCircle(mNativeCanvasWrapper, cx, cy, radius, paint.getNativeInstance());
     }
 
@@ -275,23 +275,23 @@
 
     public void drawLine(float startX, float startY, float stopX, float stopY,
             @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawLine(mNativeCanvasWrapper, startX, startY, stopX, stopY, paint.getNativeInstance());
     }
 
     public void drawLines(@Size(multiple = 4) @NonNull float[] pts, int offset, int count,
             @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawLines(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance());
     }
 
     public void drawLines(@Size(multiple = 4) @NonNull float[] pts, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         drawLines(pts, 0, pts.length, paint);
     }
 
     public void drawOval(float left, float top, float right, float bottom, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawOval(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance());
     }
 
@@ -299,18 +299,19 @@
         if (oval == null) {
             throw new NullPointerException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         drawOval(oval.left, oval.top, oval.right, oval.bottom, paint);
     }
 
     public void drawPaint(@NonNull Paint paint) {
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawPaint(mNativeCanvasWrapper, paint.getNativeInstance());
     }
 
     public void drawPatch(@NonNull NinePatch patch, @NonNull Rect dst, @Nullable Paint paint) {
         Bitmap bitmap = patch.getBitmap();
         throwIfCannotDraw(bitmap);
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
         nDrawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk,
                 dst.left, dst.top, dst.right, dst.bottom, nativePaint,
@@ -320,7 +321,7 @@
     public void drawPatch(@NonNull NinePatch patch, @NonNull RectF dst, @Nullable Paint paint) {
         Bitmap bitmap = patch.getBitmap();
         throwIfCannotDraw(bitmap);
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
         nDrawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk,
                 dst.left, dst.top, dst.right, dst.bottom, nativePaint,
@@ -328,7 +329,7 @@
     }
 
     public void drawPath(@NonNull Path path, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         if (path.isSimplePath && path.rects != null) {
             nDrawRegion(mNativeCanvasWrapper, path.rects.mNativeRegion, paint.getNativeInstance());
         } else {
@@ -337,18 +338,18 @@
     }
 
     public void drawPoint(float x, float y, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawPoint(mNativeCanvasWrapper, x, y, paint.getNativeInstance());
     }
 
     public void drawPoints(@Size(multiple = 2) float[] pts, int offset, int count,
             @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawPoints(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance());
     }
 
     public void drawPoints(@Size(multiple = 2) @NonNull float[] pts, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         drawPoints(pts, 0, pts.length, paint);
     }
 
@@ -359,7 +360,7 @@
         if (index < 0 || index + count > text.length || count * 2 > pos.length) {
             throw new IndexOutOfBoundsException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         for (int i = 0; i < count; i++) {
             drawText(text, index + i, 1, pos[i * 2], pos[i * 2 + 1], paint);
         }
@@ -368,22 +369,22 @@
     @Deprecated
     public void drawPosText(@NonNull String text, @NonNull @Size(multiple = 2) float[] pos,
             @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         drawPosText(text.toCharArray(), 0, text.length(), pos, paint);
     }
 
     public void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawRect(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance());
     }
 
     public void drawRect(@NonNull Rect r, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         drawRect(r.left, r.top, r.right, r.bottom, paint);
     }
 
     public void drawRect(@NonNull RectF rect, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawRect(mNativeCanvasWrapper,
                 rect.left, rect.top, rect.right, rect.bottom, paint.getNativeInstance());
     }
@@ -394,13 +395,13 @@
 
     public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
             @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawRoundRect(mNativeCanvasWrapper, left, top, right, bottom, rx, ry,
                 paint.getNativeInstance());
     }
 
     public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         drawRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint);
     }
 
@@ -410,7 +411,7 @@
      */
     public void drawDoubleRoundRect(@NonNull RectF outer, float outerRx, float outerRy,
             @NonNull RectF inner, float innerRx, float innerRy, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         float outerLeft = outer.left;
         float outerTop = outer.top;
         float outerRight = outer.right;
@@ -431,7 +432,7 @@
      */
     public void drawDoubleRoundRect(@NonNull RectF outer, @NonNull float[] outerRadii,
             @NonNull RectF inner, @NonNull float[] innerRadii, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         if (innerRadii == null || outerRadii == null
                 || innerRadii.length != 8 || outerRadii.length != 8) {
             throw new IllegalArgumentException("Both inner and outer radii arrays must contain "
@@ -509,7 +510,7 @@
                 (text.length - index - count)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawText(mNativeCanvasWrapper, text, index, count, x, y, paint.mBidiFlags,
                 paint.getNativeInstance());
     }
@@ -519,7 +520,7 @@
         if ((start | end | (end - start) | (text.length() - end)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         if (text instanceof String || text instanceof SpannedString ||
                 text instanceof SpannableString) {
             nDrawText(mNativeCanvasWrapper, text.toString(), start, end, x, y,
@@ -537,7 +538,7 @@
     }
 
     public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawText(mNativeCanvasWrapper, text, 0, text.length(), x, y, paint.mBidiFlags,
                 paint.getNativeInstance());
     }
@@ -547,7 +548,7 @@
         if ((start | end | (end - start) | (text.length() - end)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawText(mNativeCanvasWrapper, text, start, end, x, y, paint.mBidiFlags,
                 paint.getNativeInstance());
     }
@@ -557,7 +558,7 @@
         if (index < 0 || index + count > text.length) {
             throw new ArrayIndexOutOfBoundsException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawTextOnPath(mNativeCanvasWrapper, text, index, count,
                 path.readOnlyNI(), hOffset, vOffset,
                 paint.mBidiFlags, paint.getNativeInstance());
@@ -566,7 +567,7 @@
     public void drawTextOnPath(@NonNull String text, @NonNull Path path, float hOffset,
             float vOffset, @NonNull Paint paint) {
         if (text.length() > 0) {
-            throwIfHasHwBitmapInSwMode(paint);
+            throwIfHasHwFeaturesInSwMode(paint);
             nDrawTextOnPath(mNativeCanvasWrapper, text, path.readOnlyNI(), hOffset, vOffset,
                     paint.mBidiFlags, paint.getNativeInstance());
         }
@@ -587,7 +588,7 @@
             throw new IndexOutOfBoundsException();
         }
 
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawTextRun(mNativeCanvasWrapper, text, index, count, contextIndex, contextCount,
                 x, y, isRtl, paint.getNativeInstance(), 0 /* measured text */);
     }
@@ -606,7 +607,7 @@
             throw new IndexOutOfBoundsException();
         }
 
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         if (text instanceof String || text instanceof SpannedString ||
                 text instanceof SpannableString) {
             nDrawTextRun(mNativeCanvasWrapper, text.toString(), start, end, contextStart,
@@ -664,7 +665,7 @@
         if (indices != null) {
             checkRange(indices.length, indexOffset, indexCount);
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawVertices(mNativeCanvasWrapper, mode.nativeInt, vertexCount, verts,
                 vertOffset, texs, texOffset, colors, colorOffset,
                 indices, indexOffset, indexCount, paint.getNativeInstance());
@@ -680,50 +681,52 @@
     /**
      * @hide
      */
-    public void setHwBitmapsInSwModeEnabled(boolean enabled) {
-        mAllowHwBitmapsInSwMode = enabled;
+    public void setHwFeaturesInSwModeEnabled(boolean enabled) {
+        mAllowHwFeaturesInSwMode = enabled;
     }
 
     /**
      * @hide
      */
-    public boolean isHwBitmapsInSwModeEnabled() {
-        return mAllowHwBitmapsInSwMode;
+    public boolean isHwFeaturesInSwModeEnabled() {
+        return mAllowHwFeaturesInSwMode;
     }
 
     /**
+     * If true throw an exception
      * @hide
      */
-    protected void onHwBitmapInSwMode() {
-        if (!mAllowHwBitmapsInSwMode) {
+    protected boolean onHwFeatureInSwMode() {
+        return !mAllowHwFeaturesInSwMode;
+    }
+
+    private void throwIfHwBitmapInSwMode(Bitmap bitmap) {
+        if (!isHardwareAccelerated() && bitmap.getConfig() == Bitmap.Config.HARDWARE
+                && onHwFeatureInSwMode()) {
             throw new IllegalArgumentException(
                     "Software rendering doesn't support hardware bitmaps");
         }
     }
 
-    private void throwIfHwBitmapInSwMode(Bitmap bitmap) {
-        if (!isHardwareAccelerated() && bitmap.getConfig() == Bitmap.Config.HARDWARE) {
-            onHwBitmapInSwMode();
-        }
-    }
-
-    private void throwIfHasHwBitmapInSwMode(Paint p) {
+    private void throwIfHasHwFeaturesInSwMode(Paint p) {
         if (isHardwareAccelerated() || p == null) {
             return;
         }
-        throwIfHasHwBitmapInSwMode(p.getShader());
+        throwIfHasHwFeaturesInSwMode(p.getShader());
     }
 
-    private void throwIfHasHwBitmapInSwMode(Shader shader) {
+    private void throwIfHasHwFeaturesInSwMode(Shader shader) {
         if (shader == null) {
             return;
         }
         if (shader instanceof BitmapShader) {
             throwIfHwBitmapInSwMode(((BitmapShader) shader).mBitmap);
-        }
-        if (shader instanceof ComposeShader) {
-            throwIfHasHwBitmapInSwMode(((ComposeShader) shader).mShaderA);
-            throwIfHasHwBitmapInSwMode(((ComposeShader) shader).mShaderB);
+        } else if (shader instanceof RuntimeShader && onHwFeatureInSwMode()) {
+            throw new IllegalArgumentException(
+                    "Software rendering doesn't support RuntimeShader");
+        } else if (shader instanceof ComposeShader) {
+            throwIfHasHwFeaturesInSwMode(((ComposeShader) shader).mShaderA);
+            throwIfHasHwFeaturesInSwMode(((ComposeShader) shader).mShaderB);
         }
     }
 
diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java
index fc7f84c..618e6dc 100644
--- a/graphics/java/android/graphics/Outline.java
+++ b/graphics/java/android/graphics/Outline.java
@@ -169,8 +169,7 @@
     }
 
     /**
-     * Sets the Outline to the rounded rect defined by the input rect, and
-     * corner radius.
+     * Sets the Outline to the rect defined by the input coordinates.
      */
     public void setRect(int left, int top, int right, int bottom) {
         setRoundRect(left, top, right, bottom, 0.0f);
@@ -184,7 +183,7 @@
     }
 
     /**
-     * Sets the Outline to the rounded rect defined by the input rect, and corner radius.
+     * Sets the Outline to the rounded rect defined by the input coordinates and corner radius.
      * <p>
      * Passing a zero radius is equivalent to calling {@link #setRect(int, int, int, int)}
      */
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 42e470b..eefad8d 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -46,6 +46,7 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Locale;
+import java.util.Objects;
 
 /**
  * The Paint class holds the style and color information about how to draw
@@ -2131,6 +2132,116 @@
     }
 
     /**
+     * Returns the font metrics value for the given text.
+     *
+     * If the text is rendered with multiple font files, this function returns the large ascent and
+     * descent that are enough for drawing all font files.
+     *
+     * The context range is used for shaping context. Some script, e.g. Arabic or Devanagari,
+     * changes letter shape based on its location or surrounding characters.
+     *
+     * @param text a text to be measured.
+     * @param start a starting offset in the text.
+     * @param count a length of the text to be measured.
+     * @param contextStart a context starting offset in the text.
+     * @param contextCount a length of the context to be used.
+     * @param isRtl true if measuring on RTL context, otherwise false.
+     * @param outMetrics the output font metrics.
+     */
+    public void getFontMetricsInt(
+            @NonNull CharSequence text,
+            @IntRange(from = 0) int start, @IntRange(from = 0) int count,
+            @IntRange(from = 0) int contextStart, @IntRange(from = 0) int contextCount,
+            boolean isRtl,
+            @NonNull FontMetricsInt outMetrics) {
+
+        if (text == null) {
+            throw new IllegalArgumentException("text must not be null");
+        }
+        if (start < 0 || start >= text.length()) {
+            throw new IllegalArgumentException("start argument is out of bounds.");
+        }
+        if (count < 0 || start + count > text.length()) {
+            throw new IllegalArgumentException("count argument is out of bounds.");
+        }
+        if (contextStart < 0 || contextStart >= text.length()) {
+            throw new IllegalArgumentException("ctxStart argument is out of bounds.");
+        }
+        if (contextCount < 0 || contextStart + contextCount > text.length()) {
+            throw new IllegalArgumentException("ctxCount argument is out of bounds.");
+        }
+        if (outMetrics == null) {
+            throw new IllegalArgumentException("outMetrics must not be null.");
+        }
+
+        if (count == 0) {
+            getFontMetricsInt(outMetrics);
+            return;
+        }
+
+        if (text instanceof String) {
+            nGetFontMetricsIntForText(mNativePaint, (String) text, start, count, contextStart,
+                    contextCount, isRtl, outMetrics);
+        } else {
+            char[] buf = TemporaryBuffer.obtain(contextCount);
+            TextUtils.getChars(text, contextStart, contextStart + contextCount, buf, 0);
+            nGetFontMetricsIntForText(mNativePaint, buf, start - contextStart, count, 0,
+                    contextCount, isRtl, outMetrics);
+        }
+
+    }
+
+    /**
+     * Returns the font metrics value for the given text.
+     *
+     * If the text is rendered with multiple font files, this function returns the large ascent and
+     * descent that are enough for drawing all font files.
+     *
+     * The context range is used for shaping context. Some script, e.g. Arabic or Devanagari,
+     * changes letter shape based on its location or surrounding characters.
+     *
+     * @param text a text to be measured.
+     * @param start a starting offset in the text.
+     * @param count a length of the text to be measured.
+     * @param contextStart a context starting offset in the text.
+     * @param contextCount a length of the context to be used.
+     * @param isRtl true if measuring on RTL context, otherwise false.
+     * @param outMetrics the output font metrics.
+     */
+    public void getFontMetricsInt(@NonNull char[] text,
+            @IntRange(from = 0) int start, @IntRange(from = 0) int count,
+            @IntRange(from = 0) int contextStart, @IntRange(from = 0) int contextCount,
+            boolean isRtl,
+            @NonNull FontMetricsInt outMetrics) {
+        if (text == null) {
+            throw new IllegalArgumentException("text must not be null");
+        }
+        if (start < 0 || start >= text.length) {
+            throw new IllegalArgumentException("start argument is out of bounds.");
+        }
+        if (count < 0 || start + count > text.length) {
+            throw new IllegalArgumentException("count argument is out of bounds.");
+        }
+        if (contextStart < 0 || contextStart >= text.length) {
+            throw new IllegalArgumentException("ctxStart argument is out of bounds.");
+        }
+        if (contextCount < 0 || contextStart + contextCount > text.length) {
+            throw new IllegalArgumentException("ctxCount argument is out of bounds.");
+        }
+        if (outMetrics == null) {
+            throw new IllegalArgumentException("outMetrics must not be null.");
+        }
+
+        if (count == 0) {
+            getFontMetricsInt(outMetrics);
+            return;
+        }
+
+        nGetFontMetricsIntForText(mNativePaint, text, start, count, contextStart, contextCount,
+                isRtl, outMetrics);
+    }
+
+    /**
      * Convenience method for callers that want to have FontMetrics values as
      * integers.
      */
@@ -2163,6 +2274,23 @@
                     " descent=" + descent + " bottom=" + bottom +
                     " leading=" + leading;
         }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof FontMetricsInt)) return false;
+            FontMetricsInt that = (FontMetricsInt) o;
+            return top == that.top
+                    && ascent == that.ascent
+                    && descent == that.descent
+                    && bottom == that.bottom
+                    && leading == that.leading;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(top, ascent, descent, bottom, leading);
+        }
     }
 
     /**
@@ -3117,6 +3245,13 @@
             int contextStart, int contextEnd, boolean isRtl, int offset);
     private static native int nGetOffsetForAdvance(long paintPtr, char[] text, int start, int end,
             int contextStart, int contextEnd, boolean isRtl, float advance);
+    private static native void nGetFontMetricsIntForText(long paintPtr, char[] text,
+            int start, int count, int ctxStart, int ctxCount, boolean isRtl,
+            FontMetricsInt outMetrics);
+    private static native void nGetFontMetricsIntForText(long paintPtr, String text,
+            int start, int count, int ctxStart, int ctxCount, boolean isRtl,
+            FontMetricsInt outMetrics);
+
 
 
     // ---------------- @FastNative ------------------------
@@ -3130,7 +3265,6 @@
     @FastNative
     private static native int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi);
 
-
     // ---------------- @CriticalNative ------------------------
 
     @CriticalNative
diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java
index 390d3d4..ee4165b 100644
--- a/graphics/java/android/graphics/Picture.java
+++ b/graphics/java/android/graphics/Picture.java
@@ -124,7 +124,7 @@
     public void endRecording() {
         verifyValid();
         if (mRecordingCanvas != null) {
-            mRequiresHwAcceleration = mRecordingCanvas.mHoldsHwBitmap;
+            mRequiresHwAcceleration = mRecordingCanvas.mUsesHwFeature;
             mRecordingCanvas = null;
             nativeEndRecording(mNativePicture);
         }
@@ -182,8 +182,10 @@
         if (mRecordingCanvas != null) {
             endRecording();
         }
-        if (mRequiresHwAcceleration && !canvas.isHardwareAccelerated()) {
-            canvas.onHwBitmapInSwMode();
+        if (mRequiresHwAcceleration && !canvas.isHardwareAccelerated()
+                && canvas.onHwFeatureInSwMode()) {
+            throw new IllegalArgumentException("Software rendering not supported for Pictures that"
+                    + " require hardware acceleration");
         }
         nativeDraw(canvas.getNativeCanvasWrapper(), mNativePicture);
     }
@@ -242,7 +244,7 @@
 
     private static class PictureCanvas extends Canvas {
         private final Picture mPicture;
-        boolean mHoldsHwBitmap;
+        boolean mUsesHwFeature;
 
         public PictureCanvas(Picture pict, long nativeCanvas) {
             super(nativeCanvas);
@@ -265,8 +267,9 @@
         }
 
         @Override
-        protected void onHwBitmapInSwMode() {
-            mHoldsHwBitmap = true;
+        protected boolean onHwFeatureInSwMode() {
+            mUsesHwFeature = true;
+            return false;
         }
     }
 }
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index b843589..ffaa4ea 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -868,7 +868,7 @@
     private void drawPatterned(@NonNull Canvas canvas) {
         final Rect bounds = mHotspotBounds;
         final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
-        boolean useCanvasProps = shouldUseCanvasProps(canvas);
+        boolean useCanvasProps = !mForceSoftware;
         if (isBounded()) {
             canvas.clipRect(getDirtyBounds());
         }
@@ -914,7 +914,11 @@
         }
         for (int i = 0; i < mRunningAnimations.size(); i++) {
             RippleAnimationSession s = mRunningAnimations.get(i);
-            if (useCanvasProps) {
+            if (!canvas.isHardwareAccelerated()) {
+                Log.e(TAG, "The RippleDrawable.STYLE_PATTERNED animation is not supported for a "
+                        + "non-hardware accelerated Canvas. Skipping animation.");
+                break;
+            } else if (useCanvasProps) {
                 RippleAnimationSession.AnimationProperties<CanvasProperty<Float>,
                         CanvasProperty<Paint>>
                         p = s.getCanvasProperties();
@@ -1002,10 +1006,6 @@
         return color;
     }
 
-    private boolean shouldUseCanvasProps(Canvas c) {
-        return !mForceSoftware && c.isHardwareAccelerated();
-    }
-
     @Override
     public void invalidateSelf() {
         invalidateSelf(true);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index df751fc..180c772 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -79,22 +79,23 @@
     }
 
     @Override
-    public void registerOrganizer() {
-        if (mAnimationController != null) {
-            throw new IllegalStateException("Must unregister the organizer before re-register.");
+    public void unregisterOrganizer() {
+        stopOverrideSplitAnimation();
+        mAnimationController = null;
+        super.unregisterOrganizer();
+    }
+
+    void startOverrideSplitAnimation() {
+        if (mAnimationController == null) {
+            mAnimationController = new TaskFragmentAnimationController(this);
         }
-        super.registerOrganizer();
-        mAnimationController = new TaskFragmentAnimationController(this);
         mAnimationController.registerRemoteAnimations();
     }
 
-    @Override
-    public void unregisterOrganizer() {
+    void stopOverrideSplitAnimation() {
         if (mAnimationController != null) {
             mAnimationController.unregisterRemoteAnimations();
-            mAnimationController = null;
         }
-        super.unregisterOrganizer();
     }
 
     /**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index b8e8b01..8f368c2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -33,6 +33,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
+import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -63,6 +64,9 @@
     private @NonNull Consumer<List<SplitInfo>> mEmbeddingCallback;
     private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
 
+    // We currently only support split activity embedding within the one root Task.
+    private final Rect mParentBounds = new Rect();
+
     public SplitController() {
         mPresenter = new SplitPresenter(new MainThreadExecutor(), this);
         ActivityThread activityThread = ActivityThread.currentActivityThread();
@@ -79,6 +83,7 @@
     public void setEmbeddingRules(@NonNull Set<EmbeddingRule> rules) {
         mSplitRules.clear();
         mSplitRules.addAll(rules);
+        updateAnimationOverride();
     }
 
     @NonNull
@@ -158,6 +163,7 @@
     @Override
     public void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken,
             @NonNull Configuration parentConfig) {
+        onParentBoundsMayChange(parentConfig.windowConfiguration.getBounds());
         TaskFragmentContainer container = getContainer(fragmentToken);
         if (container != null) {
             mPresenter.updateContainer(container);
@@ -165,6 +171,51 @@
         }
     }
 
+    private void onParentBoundsMayChange(Activity activity) {
+        if (activity.isFinishing()) {
+            return;
+        }
+
+        onParentBoundsMayChange(mPresenter.getParentContainerBounds(activity));
+    }
+
+    private void onParentBoundsMayChange(Rect parentBounds) {
+        if (!parentBounds.isEmpty() && !mParentBounds.equals(parentBounds)) {
+            mParentBounds.set(parentBounds);
+            updateAnimationOverride();
+        }
+    }
+
+    /**
+     * Updates if we should override transition animation. We only want to override if the Task
+     * bounds is large enough for at least one split rule.
+     */
+    private void updateAnimationOverride() {
+        if (mParentBounds.isEmpty()) {
+            // We don't know about the parent bounds yet.
+            return;
+        }
+
+        // Check if the parent container bounds can support any split rule.
+        boolean supportSplit = false;
+        for (EmbeddingRule rule : mSplitRules) {
+            if (!(rule instanceof SplitRule)) {
+                continue;
+            }
+            if (mPresenter.shouldShowSideBySide(mParentBounds, (SplitRule) rule)) {
+                supportSplit = true;
+                break;
+            }
+        }
+
+        // We only want to override if it supports split.
+        if (supportSplit) {
+            mPresenter.startOverrideSplitAnimation();
+        } else {
+            mPresenter.stopOverrideSplitAnimation();
+        }
+    }
+
     void onActivityCreated(@NonNull Activity launchedActivity) {
         handleActivityCreated(launchedActivity);
         updateCallbackIfNecessary();
@@ -180,6 +231,11 @@
         final TaskFragmentContainer currentContainer = getContainerWithActivity(
                 launchedActivity.getActivityToken());
 
+        if (currentContainer == null) {
+            // Initial check before any TaskFragment is created.
+            onParentBoundsMayChange(launchedActivity);
+        }
+
         // Check if the activity is configured to always be expanded.
         if (shouldExpand(launchedActivity, null, splitRules)) {
             if (shouldContainerBeExpanded(currentContainer)) {
@@ -257,6 +313,8 @@
             // onTaskFragmentParentInfoChanged
             return;
         }
+        // The bounds of the container may have been changed.
+        onParentBoundsMayChange(activity);
 
         // Check if activity requires a placeholder
         launchPlaceholderIfNecessary(activity);
@@ -346,7 +404,7 @@
     TaskFragmentContainer getTopActiveContainer() {
         for (int i = mContainers.size() - 1; i >= 0; i--) {
             TaskFragmentContainer container = mContainers.get(i);
-            if (!container.isFinished()) {
+            if (!container.isFinished() && container.getTopNonFinishingActivity() != null) {
                 return container;
             }
         }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
index 3c7d2de..a801dc8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
@@ -37,32 +37,42 @@
 
     private final TaskFragmentOrganizer mOrganizer;
     private final TaskFragmentAnimationRunner mRemoteRunner = new TaskFragmentAnimationRunner();
+    private final RemoteAnimationDefinition mDefinition;
+    private boolean mIsRegister;
 
     TaskFragmentAnimationController(TaskFragmentOrganizer organizer) {
         mOrganizer = organizer;
+        mDefinition = new RemoteAnimationDefinition();
+        final RemoteAnimationAdapter animationAdapter =
+                new RemoteAnimationAdapter(mRemoteRunner, 0, 0, true /* changeNeedsSnapshot */);
+        mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, animationAdapter);
+        mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, animationAdapter);
+        mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_OPEN, animationAdapter);
+        mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, animationAdapter);
+        mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, animationAdapter);
+        mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_CLOSE, animationAdapter);
+        mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, animationAdapter);
     }
 
     void registerRemoteAnimations() {
         if (DEBUG) {
             Log.v(TAG, "registerRemoteAnimations");
         }
-        final RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
-        final RemoteAnimationAdapter animationAdapter =
-                new RemoteAnimationAdapter(mRemoteRunner, 0, 0, true /* changeNeedsSnapshot */);
-        definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, animationAdapter);
-        definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, animationAdapter);
-        definition.addRemoteAnimation(TRANSIT_OLD_TASK_OPEN, animationAdapter);
-        definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, animationAdapter);
-        definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, animationAdapter);
-        definition.addRemoteAnimation(TRANSIT_OLD_TASK_CLOSE, animationAdapter);
-        definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, animationAdapter);
-        mOrganizer.registerRemoteAnimations(definition);
+        if (mIsRegister) {
+            return;
+        }
+        mOrganizer.registerRemoteAnimations(mDefinition);
+        mIsRegister = true;
     }
 
     void unregisterRemoteAnimations() {
         if (DEBUG) {
             Log.v(TAG, "unregisterRemoteAnimations");
         }
+        if (!mIsRegister) {
+            return;
+        }
         mOrganizer.unregisterRemoteAnimations();
+        mIsRegister = false;
     }
 }
diff --git a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_gain_animation.xml b/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_gain_animation.xml
deleted file mode 100644
index 29d9b25..0000000
--- a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_gain_animation.xml
+++ /dev/null
@@ -1,20 +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.
--->
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
-    android:propertyName="alpha"
-    android:valueTo="1"
-    android:interpolator="@android:interpolator/fast_out_slow_in"
-    android:duration="100" />
diff --git a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml b/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml
deleted file mode 100644
index 70f553b..0000000
--- a/libs/WindowManager/Shell/res/anim/tv_pip_controls_focus_loss_animation.xml
+++ /dev/null
@@ -1,20 +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.
--->
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
-    android:propertyName="alpha"
-    android:valueTo="0"
-    android:interpolator="@android:interpolator/fast_out_slow_in"
-    android:duration="100" />
diff --git a/libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_in_animation.xml b/libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_in_animation.xml
deleted file mode 100644
index 29d9b25..0000000
--- a/libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_in_animation.xml
+++ /dev/null
@@ -1,20 +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.
--->
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
-    android:propertyName="alpha"
-    android:valueTo="1"
-    android:interpolator="@android:interpolator/fast_out_slow_in"
-    android:duration="100" />
diff --git a/libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_out_animation.xml b/libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_out_animation.xml
deleted file mode 100644
index 70f553b..0000000
--- a/libs/WindowManager/Shell/res/anim/tv_pip_menu_fade_out_animation.xml
+++ /dev/null
@@ -1,20 +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.
--->
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
-    android:propertyName="alpha"
-    android:valueTo="0"
-    android:interpolator="@android:interpolator/fast_out_slow_in"
-    android:duration="100" />
diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_down.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_down.xml
new file mode 100644
index 0000000..d8f3561
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/pip_ic_move_down.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:fillColor="@color/tv_pip_menu_focus_border"
+        android:pathData="M7,10l5,5 5,-5H7z"/>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_left.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_left.xml
new file mode 100644
index 0000000..3e0011c
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/pip_ic_move_left.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:fillColor="@color/tv_pip_menu_focus_border"
+        android:pathData="M14,7l-5,5 5,5V7z"/>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_right.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_right.xml
new file mode 100644
index 0000000..f6b3c72
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/pip_ic_move_right.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:fillColor="@color/tv_pip_menu_focus_border"
+        android:pathData="M10,17l5,-5 -5,-5v10z"/>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_up.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_up.xml
new file mode 100644
index 0000000..1a34462
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/pip_ic_move_up.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:fillColor="@color/tv_pip_menu_focus_border"
+        android:pathData="M7,14l5,-5 5,5H7z"/>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/pip_ic_move_white.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_move_white.xml
new file mode 100644
index 0000000..37f4c87
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/pip_ic_move_white.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+
+    <path
+        android:pathData="M11,5.83L11,10h2L13,5.83l1.83,1.83 1.41,-1.42L12,2 7.76,6.24l1.41,1.42zM17.76,7.76l-1.42,1.41L18.17,11L14,11v2h4.17l-1.83,1.83 1.42,1.41L22,12zM13,18.17L13,14h-2v4.17l-1.83,-1.83 -1.41,1.42L12,22l4.24,-4.24 -1.41,-1.42zM10,13v-2L5.83,11l1.83,-1.83 -1.42,-1.41L2,12l4.24,4.24 1.42,-1.41L5.83,13z"
+        android:fillColor="#FFFFFF" />
+
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/background_panel.xml b/libs/WindowManager/Shell/res/layout/background_panel.xml
new file mode 100644
index 0000000..c3569d8
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/background_panel.xml
@@ -0,0 +1,26 @@
+<?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
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/background_panel_layout"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:gravity="center_horizontal | center_vertical"
+    android:background="@android:color/transparent">
+</LinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index 5b90c99..b56b114 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -15,57 +15,101 @@
     limitations under the License.
 -->
 <!-- Layout for TvPipMenuView -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/tv_pip_menu"
              android:layout_width="match_parent"
              android:layout_height="match_parent">
 
-    <FrameLayout
+    <HorizontalScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_centerHorizontal="true"
+        android:gravity="center"
+        android:scrollbars="none"
+        android:layout_centerInParent="true"
+        android:layout_margin="@dimen/pip_menu_outer_space">
+
+        <LinearLayout
+            android:id="@+id/tv_pip_menu_action_buttons"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:paddingStart="@dimen/pip_menu_button_wrapper_margin"
+            android:paddingEnd="@dimen/pip_menu_button_wrapper_margin"
+            android:gravity="center"
+            android:orientation="horizontal"
+            android:alpha="0">
+
+            <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+                android:id="@+id/tv_pip_menu_fullscreen_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:src="@drawable/pip_ic_fullscreen_white"
+                android:text="@string/pip_fullscreen" />
+
+            <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+                android:id="@+id/tv_pip_menu_move_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:src="@drawable/pip_ic_move_white"
+                android:text="@String/pip_move" />
+
+            <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+                android:id="@+id/tv_pip_menu_close_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:src="@drawable/pip_ic_close_white"
+                android:text="@string/pip_close" />
+
+            <!-- More TvPipMenuActionButtons may be added here at runtime. -->
+
+        </LinearLayout>
+    </HorizontalScrollView>
+
+    <View
         android:id="@+id/tv_pip_menu_frame"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:alpha="0" >
+        android:alpha="0"
+        android:layout_margin="@dimen/pip_menu_outer_space_frame"
+        android:background="@drawable/tv_pip_menu_border"/>
 
-        <HorizontalScrollView
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_centerHorizontal="true"
-            android:gravity="center"
-            android:scrollbars="none"
-            android:requiresFadingEdge="vertical"
-            android:fadingEdgeLength="30dp">
+    <ImageView
+        android:id="@+id/tv_pip_menu_arrow_up"
+        android:layout_width="@dimen/pip_menu_arrow_size"
+        android:layout_height="@dimen/pip_menu_arrow_size"
+        android:layout_centerHorizontal="true"
+        android:layout_alignParentTop="true"
+        android:alpha="0"
+        android:elevation="@dimen/pip_menu_arrow_elevation"
+        android:src="@drawable/pip_ic_move_up" />
 
-            <LinearLayout
-                android:id="@+id/tv_pip_menu_action_buttons"
-                android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                android:paddingStart="@dimen/pip_menu_button_wrapper_margin"
-                android:paddingEnd="@dimen/pip_menu_button_wrapper_margin"
-                android:gravity="center"
-                android:orientation="horizontal">
+    <ImageView
+        android:id="@+id/tv_pip_menu_arrow_right"
+        android:layout_width="@dimen/pip_menu_arrow_size"
+        android:layout_height="@dimen/pip_menu_arrow_size"
+        android:layout_centerVertical="true"
+        android:layout_alignParentRight="true"
+        android:alpha="0"
+        android:elevation="@dimen/pip_menu_arrow_elevation"
+        android:src="@drawable/pip_ic_move_right" />
 
-                <com.android.wm.shell.pip.tv.TvPipMenuActionButton
-                    android:id="@+id/tv_pip_menu_fullscreen_button"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:src="@drawable/pip_ic_fullscreen_white"
-                    android:text="@string/pip_fullscreen" />
+    <ImageView
+        android:id="@+id/tv_pip_menu_arrow_down"
+        android:layout_width="@dimen/pip_menu_arrow_size"
+        android:layout_height="@dimen/pip_menu_arrow_size"
+        android:layout_centerHorizontal="true"
+        android:layout_alignParentBottom="true"
+        android:alpha="0"
+        android:elevation="@dimen/pip_menu_arrow_elevation"
+        android:src="@drawable/pip_ic_move_down" />
 
-                <com.android.wm.shell.pip.tv.TvPipMenuActionButton
-                    android:id="@+id/tv_pip_menu_close_button"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:src="@drawable/pip_ic_close_white"
-                    android:text="@string/pip_close" />
-
-                <!-- More TvPipMenuActionButtons may be added here at runtime. -->
-
-            </LinearLayout>
-        </HorizontalScrollView>
-
-        <View
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:background="@drawable/tv_pip_menu_border"/>
-    </FrameLayout>
-</FrameLayout>
+    <ImageView
+        android:id="@+id/tv_pip_menu_arrow_left"
+        android:layout_width="@dimen/pip_menu_arrow_size"
+        android:layout_height="@dimen/pip_menu_arrow_size"
+        android:layout_centerVertical="true"
+        android:layout_alignParentLeft="true"
+        android:alpha="0"
+        android:elevation="@dimen/pip_menu_arrow_elevation"
+        android:src="@drawable/pip_ic_move_left" />
+</RelativeLayout>
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index e41ebc4..558ec51 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -21,7 +21,14 @@
     <dimen name="pip_menu_icon_size">20dp</dimen>
     <dimen name="pip_menu_button_margin">4dp</dimen>
     <dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
-    <dimen name="pip_menu_border_width">2dp</dimen>
-    <dimen name="pip_menu_border_radius">0dp</dimen>
+    <dimen name="pip_menu_border_width">4dp</dimen>
+    <dimen name="pip_menu_border_radius">4dp</dimen>
+    <dimen name="pip_menu_outer_space">24dp</dimen>
+
+    <!-- outer space minus border width -->
+    <dimen name="pip_menu_outer_space_frame">20dp</dimen>
+
+    <dimen name="pip_menu_arrow_size">24dp</dimen>
+    <dimen name="pip_menu_arrow_elevation">5dp</dimen>
 </resources>
 
diff --git a/libs/WindowManager/Shell/res/values/colors_tv.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml
index 17387fa..08d3cef 100644
--- a/libs/WindowManager/Shell/res/values/colors_tv.xml
+++ b/libs/WindowManager/Shell/res/values/colors_tv.xml
@@ -19,6 +19,6 @@
     <color name="tv_pip_menu_icon_unfocused">#E8EAED</color>
     <color name="tv_pip_menu_icon_disabled">#80868B</color>
     <color name="tv_pip_menu_icon_bg_focused">#E8EAED</color>
-    <color name="tv_pip_menu_icon_bg_unfocused">#777777</color>
-    <color name="tv_pip_menu_focus_border">#CCE8EAED</color>
+    <color name="tv_pip_menu_icon_bg_unfocused">#990E0E0F</color>
+    <color name="tv_pip_menu_focus_border">#E8EAED</color>
 </resources>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/strings_tv.xml b/libs/WindowManager/Shell/res/values/strings_tv.xml
index 2dfdcab..730d808 100644
--- a/libs/WindowManager/Shell/res/values/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values/strings_tv.xml
@@ -30,5 +30,8 @@
 
     <!-- Button to move picture-in-picture (PIP) screen to the fullscreen in PIP menu [CHAR LIMIT=30] -->
     <string name="pip_fullscreen">Full screen</string>
+
+    <!-- Button to move picture-in-picture (PIP) via DPAD in the PIP menu [CHAR LIMIT=30] -->
+    <string name="pip_move">Move PIP</string>
 </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 22bec3d..6823639 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
@@ -1348,7 +1348,7 @@
 
         mStackView.updateContentDescription();
 
-        mStackView.updateBubblesClickableStates();
+        mStackView.updateBubblesAcessibillityStates();
     }
 
     @VisibleForTesting
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
index 9374da4..f878a46 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
@@ -231,8 +231,9 @@
      * Fade animation for consecutive flyouts.
      */
     void animateUpdate(Bubble.FlyoutMessage flyoutMessage, PointF stackPos,
-            boolean hideDot, Runnable onHide) {
+            boolean hideDot, float[] dotCenter, Runnable onHide) {
         mOnHide = onHide;
+        mDotCenter = dotCenter;
         final Runnable afterFadeOut = () -> {
             updateFlyoutMessage(flyoutMessage);
             // Wait for TextViews to layout with updated height.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 7bf4439..a477bd7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1486,19 +1486,63 @@
     }
 
     /**
-     * Update bubbles' icon views clickable states.
+     * Update bubbles' icon views accessibility states.
      */
-    public void updateBubblesClickableStates() {
+    public void updateBubblesAcessibillityStates() {
         for (int i = 0; i < mBubbleData.getBubbles().size(); i++) {
-            final Bubble bubble = mBubbleData.getBubbles().get(i);
-            if (bubble.getIconView() != null) {
-                if (mIsExpanded) {
-                    // when stack is expanded all bubbles are clickable
-                    bubble.getIconView().setClickable(true);
-                } else {
-                    // when stack is collapsed, only the top bubble needs to be clickable,
-                    // so that a11y ignores all the inaccessible bubbles in the stack
-                    bubble.getIconView().setClickable(i == 0);
+            Bubble prevBubble = i > 0 ? mBubbleData.getBubbles().get(i - 1) : null;
+            Bubble bubble = mBubbleData.getBubbles().get(i);
+
+            View bubbleIconView = bubble.getIconView();
+            if (bubbleIconView == null) {
+                continue;
+            }
+
+            if (mIsExpanded) {
+                // when stack is expanded
+                // all bubbles are important for accessibility
+                bubbleIconView
+                        .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+
+                View prevBubbleIconView = prevBubble != null ? prevBubble.getIconView() : null;
+
+                if (prevBubbleIconView != null) {
+                    bubbleIconView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+                        @Override
+                        public void onInitializeAccessibilityNodeInfo(View v,
+                                AccessibilityNodeInfo info) {
+                            super.onInitializeAccessibilityNodeInfo(v, info);
+                            info.setTraversalAfter(prevBubbleIconView);
+                        }
+                    });
+                }
+            } else {
+                // when stack is collapsed, only the top bubble is important for accessibility,
+                bubbleIconView.setImportantForAccessibility(
+                        i == 0 ? View.IMPORTANT_FOR_ACCESSIBILITY_YES :
+                                View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+            }
+        }
+
+        if (mIsExpanded) {
+            // make the overflow bubble last in the accessibility traversal order
+
+            View bubbleOverflowIconView =
+                    mBubbleOverflow != null ? mBubbleOverflow.getIconView() : null;
+            if (bubbleOverflowIconView != null && !mBubbleData.getBubbles().isEmpty()) {
+                Bubble lastBubble =
+                        mBubbleData.getBubbles().get(mBubbleData.getBubbles().size() - 1);
+                View lastBubbleIconView = lastBubble.getIconView();
+                if (lastBubbleIconView != null) {
+                    bubbleOverflowIconView.setAccessibilityDelegate(
+                            new View.AccessibilityDelegate() {
+                                @Override
+                                public void onInitializeAccessibilityNodeInfo(View v,
+                                        AccessibilityNodeInfo info) {
+                                    super.onInitializeAccessibilityNodeInfo(v, info);
+                                    info.setTraversalAfter(lastBubbleIconView);
+                                }
+                            });
                 }
             }
         }
@@ -2516,6 +2560,7 @@
             if (mFlyout.getVisibility() == View.VISIBLE) {
                 mFlyout.animateUpdate(bubble.getFlyoutMessage(),
                         mStackAnimationController.getStackPosition(), !bubble.showDot(),
+                        bubble.getIconView().getDotCenter(),
                         mAfterFlyoutHidden /* onHide */);
             } else {
                 mFlyout.setVisibility(INVISIBLE);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index ad9ebb2..36e55ba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -137,14 +137,16 @@
             return;
         }
 
-        if (mIcon == null) {
-            // TODO: add fade-in animation.
+        if (mBackgroundLeash == null) {
             mBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash,
                     RESIZING_BACKGROUND_SURFACE_NAME, mSurfaceSession);
             t.setColor(mBackgroundLeash, getResizingBackgroundColor(resizingTask))
                     .setLayer(mBackgroundLeash, SPLIT_DIVIDER_LAYER - 1)
                     .show(mBackgroundLeash);
+        }
 
+        if (mIcon == null && resizingTask.topActivityInfo != null) {
+            // TODO: add fade-in animation.
             mIcon = mIconProvider.getIcon(resizingTask.topActivityInfo);
             mResizingIconView.setImageDrawable(mIcon);
             mResizingIconView.setVisibility(View.VISIBLE);
@@ -168,12 +170,16 @@
             return;
         }
 
+        if (mBackgroundLeash != null) {
+            t.remove(mBackgroundLeash);
+            mBackgroundLeash = null;
+        }
+
         if (mIcon != null) {
             mResizingIconView.setVisibility(View.GONE);
             mResizingIconView.setImageDrawable(null);
-            t.remove(mBackgroundLeash).hide(mIconLeash);
+            t.hide(mIconLeash);
             mIcon = null;
-            mBackgroundLeash = null;
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
index 711a0ac..f91d7e2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -30,7 +30,6 @@
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipMediaController;
 import com.android.wm.shell.pip.PipSnapAlgorithm;
@@ -39,6 +38,7 @@
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipTransitionState;
 import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm;
 import com.android.wm.shell.pip.tv.TvPipController;
 import com.android.wm.shell.pip.tv.TvPipMenuController;
 import com.android.wm.shell.pip.tv.TvPipNotificationController;
@@ -61,7 +61,7 @@
     static Optional<Pip> providePip(
             Context context,
             PipBoundsState pipBoundsState,
-            PipBoundsAlgorithm pipBoundsAlgorithm,
+            TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
             PipTaskOrganizer pipTaskOrganizer,
             TvPipMenuController tvPipMenuController,
             PipMediaController pipMediaController,
@@ -74,7 +74,7 @@
                 TvPipController.create(
                         context,
                         pipBoundsState,
-                        pipBoundsAlgorithm,
+                        tvPipBoundsAlgorithm,
                         pipTaskOrganizer,
                         pipTransitionController,
                         tvPipMenuController,
@@ -93,9 +93,9 @@
 
     @WMSingleton
     @Provides
-    static PipBoundsAlgorithm providePipBoundsAlgorithm(Context context,
+    static TvPipBoundsAlgorithm provideTvPipBoundsAlgorithm(Context context,
             PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) {
-        return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm);
+        return new TvPipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm);
     }
 
     @WMSingleton
@@ -109,10 +109,11 @@
     @Provides
     static PipTransitionController provideTvPipTransition(
             Transitions transitions, ShellTaskOrganizer shellTaskOrganizer,
-            PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipAnimationController pipAnimationController,
+            TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
             PipBoundsState pipBoundsState, TvPipMenuController pipMenuController) {
         return new TvPipTransition(pipBoundsState, pipMenuController,
-                pipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer);
+                tvPipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer);
     }
 
     @WMSingleton
@@ -156,7 +157,7 @@
             SyncTransactionQueue syncTransactionQueue,
             PipBoundsState pipBoundsState,
             PipTransitionState pipTransitionState,
-            PipBoundsAlgorithm pipBoundsAlgorithm,
+            TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
             PipAnimationController pipAnimationController,
             PipTransitionController pipTransitionController,
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
@@ -166,7 +167,7 @@
             PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new PipTaskOrganizer(context,
-                syncTransactionQueue, pipTransitionState, pipBoundsState, pipBoundsAlgorithm,
+                syncTransactionQueue, pipTransitionState, pipBoundsState, tvPipBoundsAlgorithm,
                 tvPipMenuController, pipAnimationController, pipSurfaceTransactionHelper,
                 pipTransitionController, splitScreenOptional, newSplitScreenOptional,
                 displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
new file mode 100644
index 0000000..c20b7d9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
@@ -0,0 +1,246 @@
+/*
+ * 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.wm.shell.onehanded;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+
+import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE;
+import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.util.Slog;
+import android.view.ContextThemeWrapper;
+import android.view.IWindow;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.SurfaceSession;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.DisplayLayout;
+
+import java.io.PrintWriter;
+
+/**
+ * Holds view hierarchy of a root surface and helps inflate a themeable view for background.
+ */
+public final class BackgroundWindowManager extends WindowlessWindowManager {
+    private static final String TAG = BackgroundWindowManager.class.getSimpleName();
+    private static final int THEME_COLOR_OFFSET = 10;
+
+    private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
+            mTransactionFactory;
+
+    private Context mContext;
+    private Rect mDisplayBounds;
+    private SurfaceControlViewHost mViewHost;
+    private SurfaceControl mLeash;
+    private View mBackgroundView;
+    private @OneHandedState.State int mCurrentState;
+
+    public BackgroundWindowManager(Context context) {
+        super(context.getResources().getConfiguration(), null /* rootSurface */,
+                null /* hostInputToken */);
+        mContext = context;
+        mTransactionFactory = SurfaceControl.Transaction::new;
+    }
+
+    @Override
+    public SurfaceControl getSurfaceControl(IWindow window) {
+        return super.getSurfaceControl(window);
+    }
+
+    @Override
+    public void setConfiguration(Configuration configuration) {
+        super.setConfiguration(configuration);
+        mContext = mContext.createConfigurationContext(configuration);
+    }
+
+    /**
+     * onConfigurationChanged events for updating background theme color.
+     */
+    public void onConfigurationChanged() {
+        if (mCurrentState == STATE_ENTERING || mCurrentState == STATE_ACTIVE) {
+            updateThemeOnly();
+        }
+    }
+
+    /**
+     * One-handed mode state changed callback
+     * @param newState of One-handed mode representing by {@link OneHandedState}
+     */
+    public void onStateChanged(int newState) {
+        mCurrentState = newState;
+    }
+
+    @Override
+    protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+        final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+                .setColorLayer()
+                .setBufferSize(mDisplayBounds.width(), mDisplayBounds.height())
+                .setFormat(PixelFormat.RGB_888)
+                .setOpaque(true)
+                .setName(TAG)
+                .setCallsite("BackgroundWindowManager#attachToParentSurface");
+        mLeash = builder.build();
+        b.setParent(mLeash);
+    }
+
+    /** Inflates background view on to the root surface. */
+    boolean initView() {
+        if (mBackgroundView != null || mViewHost != null) {
+            return false;
+        }
+
+        mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+        mBackgroundView = (View) LayoutInflater.from(mContext)
+                .inflate(R.layout.background_panel, null /* root */);
+        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                mDisplayBounds.width(), mDisplayBounds.height(), 0 /* TYPE NONE */,
+                FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH
+                        | FLAG_SLIPPERY, PixelFormat.TRANSLUCENT);
+        lp.token = new Binder();
+        lp.setTitle("background-panel");
+        lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+        mBackgroundView.setBackgroundColor(getThemeColorForBackground());
+        mViewHost.setView(mBackgroundView, lp);
+        return true;
+    }
+
+    /**
+     * Called when onDisplayAdded() or onDisplayRemoved() callback.
+     * @param displayLayout The latest {@link DisplayLayout} for display bounds.
+     */
+    public void onDisplayChanged(DisplayLayout displayLayout) {
+        // One-handed mode is only available on portrait.
+        if (displayLayout.height() > displayLayout.width()) {
+            mDisplayBounds = new Rect(0, 0, displayLayout.width(), displayLayout.height());
+        } else {
+            mDisplayBounds = new Rect(0, 0, displayLayout.height(), displayLayout.width());
+        }
+    }
+
+    private void updateThemeOnly() {
+        if (mBackgroundView == null || mViewHost == null || mLeash == null) {
+            Slog.w(TAG, "Background view or SurfaceControl does not exist when trying to "
+                    + "update theme only!");
+            return;
+        }
+
+        WindowManager.LayoutParams lp = (WindowManager.LayoutParams)
+                mBackgroundView.getLayoutParams();
+        mBackgroundView.setBackgroundColor(getThemeColorForBackground());
+        mViewHost.setView(mBackgroundView, lp);
+    }
+
+    /**
+     * Shows the background layer when One-handed mode triggered.
+     */
+    public void showBackgroundLayer() {
+        if (!initView()) {
+            updateThemeOnly();
+            return;
+        }
+        if (mLeash == null) {
+            Slog.w(TAG, "SurfaceControl mLeash is null, can't show One-handed mode "
+                    + "background panel!");
+            return;
+        }
+
+        mTransactionFactory.getTransaction()
+                .setAlpha(mLeash, 1.0f)
+                .setLayer(mLeash, -1 /* at bottom-most layer */)
+                .show(mLeash)
+                .apply();
+    }
+
+    /**
+     * Remove the leash of background layer after stop One-handed mode.
+     */
+    public void removeBackgroundLayer() {
+        if (mBackgroundView != null) {
+            mBackgroundView = null;
+        }
+
+        if (mViewHost != null) {
+            mViewHost.release();
+            mViewHost = null;
+        }
+
+        if (mLeash != null) {
+            mTransactionFactory.getTransaction().remove(mLeash).apply();
+            mLeash = null;
+        }
+    }
+
+    /**
+     * Gets {@link SurfaceControl} of the background layer.
+     * @return {@code null} if not exist.
+     */
+    @Nullable
+    SurfaceControl getSurfaceControl() {
+        return mLeash;
+    }
+
+    private int getThemeColor() {
+        final Context themedContext = new ContextThemeWrapper(mContext,
+                com.android.internal.R.style.Theme_DeviceDefault_DayNight);
+        return themedContext.getColor(R.color.one_handed_tutorial_background_color);
+    }
+
+    int getThemeColorForBackground() {
+        final int origThemeColor = getThemeColor();
+        return android.graphics.Color.argb(Color.alpha(origThemeColor),
+                Color.red(origThemeColor) - THEME_COLOR_OFFSET,
+                Color.green(origThemeColor) - THEME_COLOR_OFFSET,
+                Color.blue(origThemeColor) - THEME_COLOR_OFFSET);
+    }
+
+    private float adjustColor(int origColor) {
+        return Math.max(origColor - THEME_COLOR_OFFSET, 0) / 255.0f;
+    }
+
+    void dump(@NonNull PrintWriter pw) {
+        final String innerPrefix = "  ";
+        pw.println(TAG);
+        pw.print(innerPrefix + "mDisplayBounds=");
+        pw.println(mDisplayBounds);
+        pw.print(innerPrefix + "mViewHost=");
+        pw.println(mViewHost);
+        pw.print(innerPrefix + "mLeash=");
+        pw.println(mLeash);
+        pw.print(innerPrefix + "mBackgroundView=");
+        pw.println(mBackgroundView);
+    }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
deleted file mode 100644
index 9e1c61a..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
+++ /dev/null
@@ -1,272 +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.wm.shell.onehanded;
-
-import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE;
-
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.view.ContextThemeWrapper;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-import android.view.animation.LinearInterpolator;
-import android.window.DisplayAreaAppearedInfo;
-import android.window.DisplayAreaInfo;
-import android.window.DisplayAreaOrganizer;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.wm.shell.R;
-import com.android.wm.shell.common.DisplayLayout;
-
-import java.io.PrintWriter;
-import java.util.List;
-import java.util.concurrent.Executor;
-
-/**
- * Manages OneHanded color background layer areas.
- * To avoid when turning the Dark theme on, users can not clearly identify
- * the screen has entered one handed mode.
- */
-public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer
-        implements OneHandedAnimationCallback, OneHandedState.OnStateChangedListener {
-    private static final String TAG = "OneHandedBackgroundPanelOrganizer";
-    private static final int THEME_COLOR_OFFSET = 10;
-    private static final int ALPHA_ANIMATION_DURATION = 200;
-
-    private final Context mContext;
-    private final SurfaceSession mSurfaceSession = new SurfaceSession();
-    private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
-            mTransactionFactory;
-
-    private @OneHandedState.State int mCurrentState;
-    private ValueAnimator mAlphaAnimator;
-
-    private float mTranslationFraction;
-    private float[] mThemeColor;
-
-    /**
-     * The background to distinguish the boundary of translated windows and empty region when
-     * one handed mode triggered.
-     */
-    private Rect mBkgBounds;
-    private Rect mStableInsets;
-
-    @Nullable
-    @VisibleForTesting
-    SurfaceControl mBackgroundSurface;
-    @Nullable
-    private SurfaceControl mParentLeash;
-
-    public OneHandedBackgroundPanelOrganizer(Context context, DisplayLayout displayLayout,
-            OneHandedSettingsUtil settingsUtil, Executor executor) {
-        super(executor);
-        mContext = context;
-        mTranslationFraction = settingsUtil.getTranslationFraction(context);
-        mTransactionFactory = SurfaceControl.Transaction::new;
-        updateThemeColors();
-    }
-
-    @Override
-    public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo,
-            @NonNull SurfaceControl leash) {
-        mParentLeash = leash;
-    }
-
-    @Override
-    public List<DisplayAreaAppearedInfo> registerOrganizer(int displayAreaFeature) {
-        final List<DisplayAreaAppearedInfo> displayAreaInfos;
-        displayAreaInfos = super.registerOrganizer(displayAreaFeature);
-        for (int i = 0; i < displayAreaInfos.size(); i++) {
-            final DisplayAreaAppearedInfo info = displayAreaInfos.get(i);
-            onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash());
-        }
-        return displayAreaInfos;
-    }
-
-    @Override
-    public void unregisterOrganizer() {
-        super.unregisterOrganizer();
-        removeBackgroundPanelLayer();
-        mParentLeash = null;
-    }
-
-    @Override
-    public void onAnimationUpdate(SurfaceControl.Transaction tx, float xPos, float yPos) {
-        final int yTopPos = (mStableInsets.top - mBkgBounds.height()) + Math.round(yPos);
-        tx.setPosition(mBackgroundSurface, 0, yTopPos);
-    }
-
-    @Nullable
-    @VisibleForTesting
-    boolean isRegistered() {
-        return mParentLeash != null;
-    }
-
-    void createBackgroundSurface() {
-        mBackgroundSurface = new SurfaceControl.Builder(mSurfaceSession)
-                .setBufferSize(mBkgBounds.width(), mBkgBounds.height())
-                .setColorLayer()
-                .setFormat(PixelFormat.RGB_888)
-                .setOpaque(true)
-                .setName("one-handed-background-panel")
-                .setCallsite("OneHandedBackgroundPanelOrganizer")
-                .build();
-
-        // TODO(185890335) Avoid Dimming for mid-range luminance wallpapers flash.
-        mAlphaAnimator = ValueAnimator.ofFloat(1.0f, 0.0f);
-        mAlphaAnimator.setInterpolator(new LinearInterpolator());
-        mAlphaAnimator.setDuration(ALPHA_ANIMATION_DURATION);
-        mAlphaAnimator.addUpdateListener(
-                animator -> detachBackgroundFromParent(animator));
-    }
-
-    void detachBackgroundFromParent(ValueAnimator animator) {
-        if (mBackgroundSurface == null || mParentLeash == null) {
-            return;
-        }
-        // TODO(185890335) Avoid Dimming for mid-range luminance wallpapers flash.
-        final float currentValue = (float) animator.getAnimatedValue();
-        final SurfaceControl.Transaction tx = mTransactionFactory.getTransaction();
-        if (currentValue == 0.0f) {
-            tx.reparent(mBackgroundSurface, null).apply();
-        } else {
-            tx.setAlpha(mBackgroundSurface, (float) animator.getAnimatedValue()).apply();
-        }
-    }
-
-    /**
-     * Called when onDisplayAdded() or onDisplayRemoved() callback.
-     *
-     * @param displayLayout The latest {@link DisplayLayout} representing current displayId
-     */
-    public void onDisplayChanged(DisplayLayout displayLayout) {
-        mStableInsets = displayLayout.stableInsets();
-        // Ensure the mBkgBounds is portrait, due to OHM only support on portrait
-        if (displayLayout.height() > displayLayout.width()) {
-            mBkgBounds = new Rect(0, 0, displayLayout.width(),
-                    Math.round(displayLayout.height() * mTranslationFraction) + mStableInsets.top);
-        } else {
-            mBkgBounds = new Rect(0, 0, displayLayout.height(),
-                    Math.round(displayLayout.width() * mTranslationFraction) + mStableInsets.top);
-        }
-    }
-
-    @VisibleForTesting
-    void onStart() {
-        if (mBackgroundSurface == null) {
-            createBackgroundSurface();
-        }
-        showBackgroundPanelLayer();
-    }
-
-    /**
-     * Called when transition finished.
-     */
-    public void onStopFinished() {
-        if (mAlphaAnimator == null) {
-            return;
-        }
-        mAlphaAnimator.start();
-    }
-
-    @VisibleForTesting
-    void showBackgroundPanelLayer() {
-        if (mParentLeash == null) {
-            return;
-        }
-
-        if (mBackgroundSurface == null) {
-            createBackgroundSurface();
-        }
-
-        // TODO(185890335) Avoid Dimming for mid-range luminance wallpapers flash.
-        if (mAlphaAnimator.isRunning()) {
-            mAlphaAnimator.end();
-        }
-
-        mTransactionFactory.getTransaction()
-                .reparent(mBackgroundSurface, mParentLeash)
-                .setAlpha(mBackgroundSurface, 1.0f)
-                .setLayer(mBackgroundSurface, -1 /* at bottom-most layer */)
-                .setColor(mBackgroundSurface, mThemeColor)
-                .show(mBackgroundSurface)
-                .apply();
-    }
-
-    @VisibleForTesting
-    void removeBackgroundPanelLayer() {
-        if (mBackgroundSurface == null) {
-            return;
-        }
-
-        mTransactionFactory.getTransaction()
-                .remove(mBackgroundSurface)
-                .apply();
-        mBackgroundSurface = null;
-    }
-
-    /**
-     * onConfigurationChanged events for updating tutorial text.
-     */
-    public void onConfigurationChanged() {
-        updateThemeColors();
-
-        if (mCurrentState != STATE_ACTIVE) {
-            return;
-        }
-        showBackgroundPanelLayer();
-    }
-
-    private void updateThemeColors() {
-        final Context themedContext = new ContextThemeWrapper(mContext,
-                com.android.internal.R.style.Theme_DeviceDefault_DayNight);
-        final int themeColor = themedContext.getColor(
-                R.color.one_handed_tutorial_background_color);
-        mThemeColor = new float[]{
-                adjustColor(Color.red(themeColor)),
-                adjustColor(Color.green(themeColor)),
-                adjustColor(Color.blue(themeColor))};
-    }
-
-    private float adjustColor(int origColor) {
-        return Math.max(origColor - THEME_COLOR_OFFSET, 0) / 255.0f;
-    }
-
-    @Override
-    public void onStateChanged(int newState) {
-        mCurrentState = newState;
-    }
-
-    void dump(@NonNull PrintWriter pw) {
-        final String innerPrefix = "  ";
-        pw.println(TAG);
-        pw.print(innerPrefix + "mBackgroundSurface=");
-        pw.println(mBackgroundSurface);
-        pw.print(innerPrefix + "mBkgBounds=");
-        pw.println(mBkgBounds);
-        pw.print(innerPrefix + "mThemeColor=");
-        pw.println(mThemeColor);
-        pw.print(innerPrefix + "mTranslationFraction=");
-        pw.println(mTranslationFraction);
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 96f82fa..48acfc1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -99,7 +99,6 @@
 
     private OneHandedEventCallback mEventCallback;
     private OneHandedDisplayAreaOrganizer mDisplayAreaOrganizer;
-    private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer;
     private OneHandedUiEventLogger mOneHandedUiEventLogger;
 
     private final DisplayController.OnDisplaysChangedListener mDisplaysChangedListener =
@@ -163,7 +162,6 @@
                 public void onStopFinished(Rect bounds) {
                     mState.setState(STATE_NONE);
                     notifyShortcutStateChanged(STATE_NONE);
-                    mBackgroundPanelOrganizer.onStopFinished();
                 }
             };
 
@@ -201,32 +199,28 @@
         OneHandedAccessibilityUtil accessibilityUtil = new OneHandedAccessibilityUtil(context);
         OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor);
         OneHandedState oneHandedState = new OneHandedState();
+        BackgroundWindowManager backgroundWindowManager = new BackgroundWindowManager(context);
         OneHandedTutorialHandler tutorialHandler = new OneHandedTutorialHandler(context,
-                settingsUtil, windowManager);
+                settingsUtil, windowManager, backgroundWindowManager);
         OneHandedAnimationController animationController =
                 new OneHandedAnimationController(context);
         OneHandedTouchHandler touchHandler = new OneHandedTouchHandler(timeoutHandler,
                 mainExecutor);
-        OneHandedBackgroundPanelOrganizer oneHandedBackgroundPanelOrganizer =
-                new OneHandedBackgroundPanelOrganizer(context, displayLayout, settingsUtil,
-                        mainExecutor);
         OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer(
                 context, displayLayout, settingsUtil, animationController, tutorialHandler,
-                oneHandedBackgroundPanelOrganizer, jankMonitor, mainExecutor);
+                jankMonitor, mainExecutor);
         OneHandedUiEventLogger oneHandedUiEventsLogger = new OneHandedUiEventLogger(uiEventLogger);
         IOverlayManager overlayManager = IOverlayManager.Stub.asInterface(
                 ServiceManager.getService(Context.OVERLAY_SERVICE));
-        return new OneHandedController(context, displayController,
-                oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler,
-                settingsUtil, accessibilityUtil, timeoutHandler, oneHandedState, jankMonitor,
-                oneHandedUiEventsLogger, overlayManager, taskStackListener, mainExecutor,
-                mainHandler);
+        return new OneHandedController(context, displayController, organizer, touchHandler,
+                tutorialHandler, settingsUtil, accessibilityUtil, timeoutHandler, oneHandedState,
+                jankMonitor, oneHandedUiEventsLogger, overlayManager, taskStackListener,
+                mainExecutor, mainHandler);
     }
 
     @VisibleForTesting
     OneHandedController(Context context,
             DisplayController displayController,
-            OneHandedBackgroundPanelOrganizer backgroundPanelOrganizer,
             OneHandedDisplayAreaOrganizer displayAreaOrganizer,
             OneHandedTouchHandler touchHandler,
             OneHandedTutorialHandler tutorialHandler,
@@ -243,7 +237,6 @@
         mContext = context;
         mOneHandedSettingsUtil = settingsUtil;
         mOneHandedAccessibilityUtil = oneHandedAccessibilityUtil;
-        mBackgroundPanelOrganizer = backgroundPanelOrganizer;
         mDisplayAreaOrganizer = displayAreaOrganizer;
         mDisplayController = displayController;
         mTouchHandler = touchHandler;
@@ -286,7 +279,6 @@
         mAccessibilityManager.addAccessibilityStateChangeListener(
                 mAccessibilityStateChangeListener);
 
-        mState.addSListeners(mBackgroundPanelOrganizer);
         mState.addSListeners(mTutorialHandler);
     }
 
@@ -368,7 +360,6 @@
                 mDisplayAreaOrganizer.getDisplayLayout().height() * mOffSetFraction);
         mOneHandedAccessibilityUtil.announcementForScreenReader(
                 mOneHandedAccessibilityUtil.getOneHandedStartDescription());
-        mBackgroundPanelOrganizer.onStart();
         mDisplayAreaOrganizer.scheduleOffset(0, yOffSet);
         mTimeoutHandler.resetTimer();
         mOneHandedUiEventLogger.writeEvent(
@@ -461,7 +452,6 @@
         }
         mDisplayAreaOrganizer.setDisplayLayout(newDisplayLayout);
         mTutorialHandler.onDisplayChanged(newDisplayLayout);
-        mBackgroundPanelOrganizer.onDisplayChanged(newDisplayLayout);
     }
 
     private ContentObserver getObserver(Runnable onChangeRunnable) {
@@ -585,7 +575,6 @@
 
         if (!mIsOneHandedEnabled) {
             mDisplayAreaOrganizer.unregisterOrganizer();
-            mBackgroundPanelOrganizer.unregisterOrganizer();
             // Do NOT register + unRegister DA in the same call
             return;
         }
@@ -594,11 +583,6 @@
             mDisplayAreaOrganizer.registerOrganizer(
                     OneHandedDisplayAreaOrganizer.FEATURE_ONE_HANDED);
         }
-
-        if (!mBackgroundPanelOrganizer.isRegistered()) {
-            mBackgroundPanelOrganizer.registerOrganizer(
-                    OneHandedBackgroundPanelOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL);
-        }
     }
 
     @VisibleForTesting
@@ -613,13 +597,12 @@
     }
 
     private void onConfigChanged(Configuration newConfig) {
-        if (mTutorialHandler == null || mBackgroundPanelOrganizer == null) {
+        if (mTutorialHandler == null) {
             return;
         }
         if (!mIsOneHandedEnabled || newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
             return;
         }
-        mBackgroundPanelOrganizer.onConfigurationChanged();
         mTutorialHandler.onConfigurationChanged();
     }
 
@@ -650,10 +633,6 @@
         pw.print(innerPrefix + "mIsSwipeToNotificationEnabled=");
         pw.println(mIsSwipeToNotificationEnabled);
 
-        if (mBackgroundPanelOrganizer != null) {
-            mBackgroundPanelOrganizer.dump(pw);
-        }
-
         if (mDisplayAreaOrganizer != null) {
             mDisplayAreaOrganizer.dump(pw);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index 87eb40c..f61d1b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -80,7 +80,6 @@
             mSurfaceControlTransactionFactory;
     private OneHandedTutorialHandler mTutorialHandler;
     private List<OneHandedTransitionCallback> mTransitionCallbacks = new ArrayList<>();
-    private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer;
 
     @VisibleForTesting
     OneHandedAnimationCallback mOneHandedAnimationCallback =
@@ -135,7 +134,6 @@
             OneHandedSettingsUtil oneHandedSettingsUtil,
             OneHandedAnimationController animationController,
             OneHandedTutorialHandler tutorialHandler,
-            OneHandedBackgroundPanelOrganizer oneHandedBackgroundGradientOrganizer,
             InteractionJankMonitor jankMonitor,
             ShellExecutor mainExecutor) {
         super(mainExecutor);
@@ -150,7 +148,6 @@
                 SystemProperties.getInt(ONE_HANDED_MODE_TRANSLATE_ANIMATION_DURATION,
                         animationDurationConfig);
         mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
-        mBackgroundPanelOrganizer = oneHandedBackgroundGradientOrganizer;
         mTutorialHandler = tutorialHandler;
     }
 
@@ -258,7 +255,6 @@
             animator.setTransitionDirection(direction)
                     .addOneHandedAnimationCallback(mOneHandedAnimationCallback)
                     .addOneHandedAnimationCallback(mTutorialHandler)
-                    .addOneHandedAnimationCallback(mBackgroundPanelOrganizer)
                     .setDuration(durationMs)
                     .start();
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
index 88f3375..04e8cf9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
@@ -32,7 +32,6 @@
 import android.content.res.TypedArray;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
-import android.os.SystemProperties;
 import android.view.ContextThemeWrapper;
 import android.view.Gravity;
 import android.view.LayoutInflater;
@@ -65,6 +64,7 @@
 
     private final float mTutorialHeightRatio;
     private final WindowManager mWindowManager;
+    private final BackgroundWindowManager mBackgroundWindowManager;
 
     private @OneHandedState.State int mCurrentState;
     private int mTutorialAreaHeight;
@@ -79,9 +79,10 @@
     private int mAlphaAnimationDurationMs;
 
     public OneHandedTutorialHandler(Context context, OneHandedSettingsUtil settingsUtil,
-            WindowManager windowManager) {
+            WindowManager windowManager, BackgroundWindowManager backgroundWindowManager) {
         mContext = context;
         mWindowManager = windowManager;
+        mBackgroundWindowManager = backgroundWindowManager;
         mTutorialHeightRatio = settingsUtil.getTranslationFraction(context);
         mAlphaAnimationDurationMs = settingsUtil.getTransitionDuration(context);
     }
@@ -110,8 +111,19 @@
     }
 
     @Override
+    public void onStartFinished(Rect bounds) {
+        fillBackgroundColor();
+    }
+
+    @Override
+    public void onStopFinished(Rect bounds) {
+        removeBackgroundSurface();
+    }
+
+    @Override
     public void onStateChanged(int newState) {
         mCurrentState = newState;
+        mBackgroundWindowManager.onStateChanged(newState);
         switch (newState) {
             case STATE_ENTERING:
                 createViewAndAttachToWindow(mContext);
@@ -126,7 +138,6 @@
             case STATE_NONE:
                 checkTransitionEnd();
                 removeTutorialFromWindowManager();
-                break;
             default:
                 break;
         }
@@ -146,6 +157,7 @@
         }
         mTutorialAreaHeight = Math.round(mDisplayBounds.height() * mTutorialHeightRatio);
         mAlphaTransitionStart = mTutorialAreaHeight * START_TRANSITION_FRACTION;
+        mBackgroundWindowManager.onDisplayChanged(displayLayout);
     }
 
     @VisibleForTesting
@@ -169,6 +181,7 @@
     private void attachTargetToWindow() {
         try {
             mWindowManager.addView(mTargetViewContainer, getTutorialTargetLayoutParams());
+            mBackgroundWindowManager.showBackgroundLayer();
         } catch (IllegalStateException e) {
             // This shouldn't happen, but if the target is already added, just update its
             // layout params.
@@ -186,6 +199,11 @@
         mTargetViewContainer = null;
     }
 
+    @VisibleForTesting
+    void removeBackgroundSurface() {
+        mBackgroundWindowManager.removeBackgroundLayer();
+    }
+
     /**
      * Returns layout params for the dismiss target, using the latest display metrics.
      */
@@ -213,9 +231,12 @@
      * onConfigurationChanged events for updating tutorial text.
      */
     public void onConfigurationChanged() {
+        mBackgroundWindowManager.onConfigurationChanged();
+
         removeTutorialFromWindowManager();
         if (mCurrentState == STATE_ENTERING || mCurrentState == STATE_ACTIVE) {
             createViewAndAttachToWindow(mContext);
+            fillBackgroundColor();
             updateThemeColor();
             checkTransitionEnd();
         }
@@ -247,6 +268,14 @@
         tutorialDesc.setTextColor(themedTextColorSecondary);
     }
 
+    private void fillBackgroundColor() {
+        if (mTargetViewContainer == null || mBackgroundWindowManager == null) {
+            return;
+        }
+        mTargetViewContainer.setBackgroundColor(
+                mBackgroundWindowManager.getThemeColorForBackground());
+    }
+
     private void setupAlphaTransition(boolean isEntering) {
         final float start = isEntering ? 0.0f : 1.0f;
         final float end = isEntering ? 1.0f : 0.0f;
@@ -282,5 +311,9 @@
         pw.println(mAlphaTransitionStart);
         pw.print(innerPrefix + "mAlphaAnimationDurationMs=");
         pw.println(mAlphaAnimationDurationMs);
+
+        if (mBackgroundWindowManager != null) {
+            mBackgroundWindowManager.dump(pw);
+        }
     }
 }
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 a4b866aa..1a3c51e 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
@@ -44,7 +44,7 @@
     private static final String TAG = PipBoundsAlgorithm.class.getSimpleName();
     private static final float INVALID_SNAP_FRACTION = -1f;
 
-    private final @NonNull PipBoundsState mPipBoundsState;
+    protected final @NonNull PipBoundsState mPipBoundsState;
     private final PipSnapAlgorithm mSnapAlgorithm;
 
     private float mDefaultSizePercent;
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 1716380..69e7836 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
@@ -155,7 +155,8 @@
 
             switch (type) {
                 case TRANSIT_EXIT_PIP:
-                    startExitAnimation(info, startTransaction, finishCallback, exitPipChange);
+                    startExitAnimation(info, startTransaction, finishTransaction, finishCallback,
+                            exitPipChange);
                     break;
                 case TRANSIT_EXIT_PIP_TO_SPLIT:
                     startExitToSplitAnimation(info, startTransaction, finishTransaction,
@@ -283,6 +284,7 @@
 
     private void startExitAnimation(@NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback,
             @NonNull TransitionInfo.Change pipChange) {
         TransitionInfo.Change displayRotationChange = null;
@@ -298,8 +300,8 @@
 
         if (displayRotationChange != null) {
             // Exiting PIP to fullscreen with orientation change.
-            startExpandAndRotationAnimation(info, startTransaction, finishCallback,
-                    displayRotationChange, pipChange);
+            startExpandAndRotationAnimation(info, startTransaction, finishTransaction,
+                    finishCallback, displayRotationChange, pipChange);
             return;
         }
 
@@ -322,6 +324,7 @@
 
     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) {
@@ -335,7 +338,6 @@
         rotator.handleClosingChanges(info, startTransaction, rotateDelta, displayW, displayH);
 
         mFinishCallback = (wct, wctCB) -> {
-            rotator.cleanUp();
             mPipOrganizer.onExitPipFinished(pipChange.getTaskInfo());
             finishCallback.onTransitionFinished(wct, wctCB);
         };
@@ -366,6 +368,7 @@
                 endBounds, startBounds, new Rect(), degree, x, y, true /* isExpanding */,
                 pipRotateDelta == ROTATION_270 /* clockwise */);
         startTransaction.apply();
+        rotator.cleanUp(finishTransaction);
 
         // Expand and rotate the pip window to fullscreen.
         final PipAnimationController.PipTransitionAnimator animator =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
new file mode 100644
index 0000000..33f3bfb
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.Gravity;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipSnapAlgorithm;
+
+/**
+ * Contains pip bounds calculations that are specific to TV.
+ */
+public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
+
+    private static final String TAG = TvPipBoundsAlgorithm.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    public TvPipBoundsAlgorithm(Context context,
+            @NonNull PipBoundsState pipBoundsState,
+            @NonNull PipSnapAlgorithm pipSnapAlgorithm) {
+        super(context, pipBoundsState, pipSnapAlgorithm);
+    }
+
+    /**
+     * The normal bounds at a different position on the screen.
+     */
+    public Rect getTvNormalBounds(int gravity) {
+        Rect normalBounds = getNormalBounds();
+        Rect insetBounds = new Rect();
+        getInsetBounds(insetBounds);
+
+        if (mPipBoundsState.isImeShowing()) {
+            if (DEBUG) Log.d(TAG, "IME showing, height: " + mPipBoundsState.getImeHeight());
+            insetBounds.bottom -= mPipBoundsState.getImeHeight();
+        }
+
+        Rect result = new Rect();
+        Gravity.apply(gravity, normalBounds.width(), normalBounds.height(), insetBounds, result);
+
+        if (DEBUG) {
+            Log.d(TAG, "normalBounds: " + normalBounds.toShortString());
+            Log.d(TAG, "insetBounds: " + insetBounds.toShortString());
+            Log.d(TAG, "gravity: " + Gravity.toString(gravity));
+            Log.d(TAG, "resultBounds: " + result.toShortString());
+        }
+
+        return result;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index b165706..de53939 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -18,6 +18,10 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.KeyEvent.KEYCODE_DPAD_DOWN;
+import static android.view.KeyEvent.KEYCODE_DPAD_LEFT;
+import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
+import static android.view.KeyEvent.KEYCODE_DPAD_UP;
 
 import android.annotation.IntDef;
 import android.app.ActivityManager;
@@ -33,6 +37,7 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.DisplayInfo;
+import android.view.Gravity;
 
 import com.android.wm.shell.R;
 import com.android.wm.shell.WindowManagerShellWrapper;
@@ -42,7 +47,6 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
 import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipMediaController;
 import com.android.wm.shell.pip.PipTaskOrganizer;
@@ -85,10 +89,12 @@
      */
     private static final int STATE_PIP_MENU = 2;
 
+    private static final int DEFAULT_GRAVITY = Gravity.BOTTOM | Gravity.RIGHT;
+
     private final Context mContext;
 
     private final PipBoundsState mPipBoundsState;
-    private final PipBoundsAlgorithm mPipBoundsAlgorithm;
+    private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
     private final PipTaskOrganizer mPipTaskOrganizer;
     private final PipMediaController mPipMediaController;
     private final TvPipNotificationController mPipNotificationController;
@@ -97,6 +103,7 @@
     private final TvPipImpl mImpl = new TvPipImpl();
 
     private @State int mState = STATE_NO_PIP;
+    private @Gravity.GravityFlags int mGravity = DEFAULT_GRAVITY;
     private int mPinnedTaskId = NONEXISTENT_TASK_ID;
 
     private int mResizeAnimationDuration;
@@ -104,7 +111,7 @@
     public static Pip create(
             Context context,
             PipBoundsState pipBoundsState,
-            PipBoundsAlgorithm pipBoundsAlgorithm,
+            TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
             PipTaskOrganizer pipTaskOrganizer,
             PipTransitionController pipTransitionController,
             TvPipMenuController tvPipMenuController,
@@ -116,7 +123,7 @@
         return new TvPipController(
                 context,
                 pipBoundsState,
-                pipBoundsAlgorithm,
+                tvPipBoundsAlgorithm,
                 pipTaskOrganizer,
                 pipTransitionController,
                 tvPipMenuController,
@@ -130,7 +137,7 @@
     private TvPipController(
             Context context,
             PipBoundsState pipBoundsState,
-            PipBoundsAlgorithm pipBoundsAlgorithm,
+            TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
             PipTaskOrganizer pipTaskOrganizer,
             PipTransitionController pipTransitionController,
             TvPipMenuController tvPipMenuController,
@@ -145,7 +152,7 @@
         mPipBoundsState = pipBoundsState;
         mPipBoundsState.setDisplayId(context.getDisplayId());
         mPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay()));
-        mPipBoundsAlgorithm = pipBoundsAlgorithm;
+        mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm;
 
         mPipMediaController = pipMediaController;
 
@@ -192,24 +199,19 @@
     public void showPictureInPictureMenu() {
         if (DEBUG) Log.d(TAG, "showPictureInPictureMenu(), state=" + stateToName(mState));
 
-        if (mState != STATE_PIP) {
+        if (mState == STATE_NO_PIP) {
             if (DEBUG) Log.d(TAG, "  > cannot open Menu from the current state.");
             return;
         }
 
         setState(STATE_PIP_MENU);
-        resizePinnedStack(STATE_PIP_MENU);
+        movePinnedStack();
     }
 
-    /**
-     * Moves Pip window to its "normal" position.
-     */
     @Override
-    public void movePipToNormalPosition() {
-        if (DEBUG) Log.d(TAG, "movePipToNormalPosition(), state=" + stateToName(mState));
-
+    public void closeMenu() {
+        if (DEBUG) Log.d(TAG, "closeMenu(), state before=" + stateToName(mState));
         setState(STATE_PIP);
-        resizePinnedStack(STATE_PIP);
     }
 
     /**
@@ -223,6 +225,69 @@
         onPipDisappeared();
     }
 
+    @Override
+    public void movePip(int keycode) {
+        if (updatePosition(keycode)) {
+            if (DEBUG) Log.d(TAG, "New gravity: " + Gravity.toString(mGravity));
+            mTvPipMenuController.updateMenu(mGravity);
+            movePinnedStack();
+        } else {
+            if (DEBUG) Log.d(TAG, "Position hasn't changed");
+        }
+    }
+
+    @Override
+    public int getPipGravity() {
+        return mGravity;
+    }
+
+    /**
+     * @return true if position changed
+     */
+    private boolean updatePosition(int keycode) {
+        if (DEBUG) Log.d(TAG, "updatePosition, keycode: " + keycode);
+
+        int updatedGravity;
+        switch (keycode) {
+            case KEYCODE_DPAD_UP:
+                updatedGravity = (mGravity & (~Gravity.BOTTOM)) | Gravity.TOP;
+                break;
+            case KEYCODE_DPAD_DOWN:
+                updatedGravity =  (mGravity & (~Gravity.TOP)) | Gravity.BOTTOM;
+                break;
+            case KEYCODE_DPAD_LEFT:
+                updatedGravity = (mGravity & (~Gravity.RIGHT)) | Gravity.LEFT;
+                break;
+            case KEYCODE_DPAD_RIGHT:
+                updatedGravity = (mGravity & (~Gravity.LEFT)) | Gravity.RIGHT;
+                break;
+            default:
+                updatedGravity = mGravity;
+        }
+
+        if (updatedGravity != mGravity) {
+            mGravity = updatedGravity;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Animate to the updated position of the PiP based on the state and position of the PiP.
+     */
+    private void movePinnedStack() {
+        if (mState == STATE_NO_PIP) {
+            return;
+        }
+
+        Rect bounds = mTvPipBoundsAlgorithm.getTvNormalBounds(mGravity);
+        if (DEBUG) Log.d(TAG, "movePinnedStack() - new pip bounds: " + bounds.toShortString());
+        mPipTaskOrganizer.scheduleAnimateResizePip(bounds,
+                mResizeAnimationDuration, rect -> {
+                    if (DEBUG) Log.d(TAG, "movePinnedStack() animation done");
+                });
+    }
+
     /**
      * Closes Pip window.
      */
@@ -234,41 +299,6 @@
         onPipDisappeared();
     }
 
-    /**
-     * Resizes the Pip task/window to the appropriate size for the given state.
-     * This is a legacy API. Now we expect that the state argument passed to it should always match
-     * the current state of the Controller. If it does not match an {@link IllegalArgumentException}
-     * will be thrown. However, if the passed state does match - we'll determine the right bounds
-     * to the state and will move Pip task/window there.
-     *
-     * @param state the to determine the Pip bounds. IMPORTANT: should always match the current
-     *              state of the Controller.
-     */
-    private void resizePinnedStack(@State int state) {
-        if (state != mState) {
-            throw new IllegalArgumentException("The passed state should match the current state!");
-        }
-        if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + stateToName(mState));
-
-        final Rect newBounds;
-        switch (mState) {
-            case STATE_PIP_MENU:
-            case STATE_PIP:
-                // Let PipBoundsAlgorithm figure out what the correct bounds are at the moment.
-                // Internally, it will get the "default" bounds from PipBoundsState and adjust them
-                // as needed to account for things like IME state (will query PipBoundsState for
-                // this information as well, so it's important to keep PipBoundsState up to date).
-                newBounds = mPipBoundsAlgorithm.getNormalBounds();
-                break;
-
-            case STATE_NO_PIP:
-            default:
-                return;
-        }
-
-        mPipTaskOrganizer.scheduleAnimateResizePip(newBounds, mResizeAnimationDuration, null);
-    }
-
     private void registerSessionListenerForCurrentUser() {
         mPipMediaController.registerSessionListenerForCurrentUser();
     }
@@ -298,6 +328,7 @@
 
         mPipNotificationController.dismiss();
         mTvPipMenuController.hideMenu();
+        mGravity = DEFAULT_GRAVITY;
         setState(STATE_NO_PIP);
         mPinnedTaskId = NONEXISTENT_TASK_ID;
     }
@@ -384,10 +415,9 @@
                         return;
                     }
                     mPipBoundsState.setImeVisibility(imeVisible, imeHeight);
-                    // "Normal" Pip bounds may have changed, so if we are in the "normal" state,
-                    // let's update the bounds.
-                    if (mState == STATE_PIP) {
-                        resizePinnedStack(STATE_PIP);
+
+                    if (mState != STATE_NO_PIP) {
+                        movePinnedStack();
                     }
                 }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipInterpolators.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipInterpolators.java
new file mode 100644
index 0000000..927c1ec
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipInterpolators.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+/**
+ * All interpolators needed for TV specific Pip animations
+ */
+public class TvPipInterpolators {
+
+    /**
+     * A standard ease-in-out curve reserved for moments of interaction (button and card states).
+     */
+    public static final Interpolator STANDARD = new PathInterpolator(0.2f, 0.1f, 0f, 1f);
+
+    /**
+     * A sharp ease-out-expo curve created for snappy but fluid browsing between cards and clusters.
+     */
+    public static final Interpolator BROWSE = new PathInterpolator(0.18f, 1f, 0.22f, 1f);
+
+    /**
+     * A smooth ease-out-expo curve created for incoming elements (forward, back, overlay).
+     */
+    public static final Interpolator ENTER = new PathInterpolator(0.12f, 1f, 0.4f, 1f);
+
+    /**
+     * A smooth ease-in-out-expo curve created for outgoing elements (forward, back, overlay).
+     */
+    public static final Interpolator EXIT = new PathInterpolator(0.4f, 1f, 0.12f, 1f);
+
+}
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 77bfa07..72ead00 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
@@ -24,13 +24,18 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ParceledListSlice;
+import android.graphics.Matrix;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Handler;
+import android.os.RemoteException;
 import android.util.Log;
 import android.view.SurfaceControl;
+import android.view.SyncRtSurfaceTransactionApplier;
 
 import androidx.annotation.Nullable;
 
+import com.android.wm.shell.R;
 import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipMediaController;
@@ -53,11 +58,33 @@
 
     private Delegate mDelegate;
     private SurfaceControl mLeash;
-    private TvPipMenuView mMenuView;
+    private TvPipMenuView mPipMenuView;
+
+    // User can actively move the PiP via the DPAD.
+    private boolean mInMoveMode;
 
     private final List<RemoteAction> mMediaActions = new ArrayList<>();
     private final List<RemoteAction> mAppActions = new ArrayList<>();
 
+    private SyncRtSurfaceTransactionApplier mApplier;
+    RectF mTmpSourceRectF = new RectF();
+    RectF mTmpDestinationRectF = new RectF();
+    Matrix mMoveTransform = new Matrix();
+
+    private final float[] mTmpValues = new float[9];
+    private final Runnable mUpdateEmbeddedMatrix = () -> {
+        if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
+            return;
+        }
+        mMoveTransform.getValues(mTmpValues);
+        try {
+            mPipMenuView.getViewRootImpl().getAccessibilityEmbeddedConnection()
+                    .setScreenMatrix(mTmpValues);
+        } catch (RemoteException e) {
+            if (DEBUG) e.printStackTrace();
+        }
+    };
+
     public TvPipMenuController(Context context, PipBoundsState pipBoundsState,
             SystemWindows systemWindows, PipMediaController pipMediaController,
             Handler mainHandler) {
@@ -107,13 +134,13 @@
     private void attachPipMenuView() {
         if (DEBUG) Log.d(TAG, "attachPipMenuView()");
 
-        if (mMenuView != null) {
+        if (mPipMenuView != null) {
             detachPipMenuView();
         }
 
-        mMenuView = new TvPipMenuView(mContext);
-        mMenuView.setListener(this);
-        mSystemWindows.addView(mMenuView,
+        mPipMenuView = new TvPipMenuView(mContext);
+        mPipMenuView.setListener(this);
+        mSystemWindows.addView(mPipMenuView,
                 getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
                 0, SHELL_ROOT_LAYER_PIP);
     }
@@ -122,38 +149,76 @@
     public void showMenu() {
         if (DEBUG) Log.d(TAG, "showMenu()");
 
-        if (mMenuView != null) {
-            Rect pipBounds = mPipBoundsState.getBounds();
-            mSystemWindows.updateViewLayout(mMenuView, getPipMenuLayoutParams(
-                    MENU_WINDOW_TITLE, pipBounds.width(), pipBounds.height()));
+        if (mPipMenuView != null) {
+            Rect menuBounds = getMenuBounds(mPipBoundsState.getBounds());
+            mSystemWindows.updateViewLayout(mPipMenuView, getPipMenuLayoutParams(
+                    MENU_WINDOW_TITLE, menuBounds.width(), menuBounds.height()));
             maybeUpdateMenuViewActions();
 
-            SurfaceControl menuSurfaceControl = mSystemWindows.getViewSurface(mMenuView);
+            SurfaceControl menuSurfaceControl = mSystemWindows.getViewSurface(mPipMenuView);
             if (menuSurfaceControl != null) {
                 SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-                t.setRelativeLayer(mMenuView.getWindowSurfaceControl(), mLeash, 1);
-                t.setPosition(menuSurfaceControl, pipBounds.left, pipBounds.top);
+                t.setRelativeLayer(mPipMenuView.getWindowSurfaceControl(), mLeash, 1);
+                t.setPosition(menuSurfaceControl, menuBounds.left, menuBounds.top);
                 t.apply();
             }
-            mMenuView.show();
+            mPipMenuView.show(mInMoveMode, mDelegate.getPipGravity());
         }
     }
 
+    void updateMenu(int gravity) {
+        mPipMenuView.showMovementHints(gravity);
+    }
+
+    private Rect getMenuBounds(Rect pipBounds) {
+        int extraSpaceInPx = mContext.getResources()
+                .getDimensionPixelSize(R.dimen.pip_menu_outer_space);
+        Rect menuBounds = new Rect(pipBounds);
+        menuBounds.inset(-extraSpaceInPx, -extraSpaceInPx);
+        return menuBounds;
+    }
+
     void hideMenu() {
-        hideMenu(true);
+        if (!isMenuVisible()) {
+            if (DEBUG) Log.d(TAG, "hideMenu() - Menu isn't visible, so don't hide");
+            return;
+        } else {
+            if (DEBUG) Log.d(TAG, "hideMenu()");
+        }
+
+        mPipMenuView.hide(mInMoveMode);
+        if (!mInMoveMode) {
+            mDelegate.closeMenu();
+        }
     }
 
-    void hideMenu(boolean movePipWindow) {
-        if (DEBUG) Log.d(TAG, "hideMenu(), movePipWindow=" + movePipWindow);
+    @Override
+    public void onEnterMoveMode() {
+        if (DEBUG) Log.d(TAG, "onEnterMoveMode - " + mInMoveMode);
+        mInMoveMode = true;
+        mPipMenuView.showMenuButtons(false);
+        mPipMenuView.showMovementHints(mDelegate.getPipGravity());
+    }
 
-        if (!isMenuVisible()) {
-            return;
+    @Override
+    public boolean onExitMoveMode() {
+        if (DEBUG) Log.d(TAG, "onExitMoveMode - " + mInMoveMode);
+        if (mInMoveMode) {
+            mInMoveMode = false;
+            mPipMenuView.showMenuButtons(true);
+            mPipMenuView.hideMovementHints();
+            return true;
         }
+        return false;
+    }
 
-        mMenuView.hide();
-        if (movePipWindow) {
-            mDelegate.movePipToNormalPosition();
+    @Override
+    public boolean onPipMovement(int keycode) {
+        if (DEBUG) Log.d(TAG, "onPipMovement - " + mInMoveMode);
+        if (mInMoveMode) {
+            mDelegate.movePip(keycode);
         }
+        return mInMoveMode;
     }
 
     @Override
@@ -163,17 +228,6 @@
         mLeash = null;
     }
 
-    private void detachPipMenuView() {
-        if (DEBUG) Log.d(TAG, "detachPipMenuView()");
-
-        if (mMenuView == null) {
-            return;
-        }
-
-        mSystemWindows.removeView(mMenuView);
-        mMenuView = null;
-    }
-
     @Override
     public void setAppActions(ParceledListSlice<RemoteAction> actions) {
         if (DEBUG) Log.d(TAG, "setAppActions()");
@@ -209,24 +263,146 @@
     }
 
     private void maybeUpdateMenuViewActions() {
-        if (mMenuView == null) {
+        if (mPipMenuView == null) {
             return;
         }
         if (!mAppActions.isEmpty()) {
-            mMenuView.setAdditionalActions(mAppActions, mMainHandler);
+            mPipMenuView.setAdditionalActions(mAppActions, mMainHandler);
         } else {
-            mMenuView.setAdditionalActions(mMediaActions, mMainHandler);
+            mPipMenuView.setAdditionalActions(mMediaActions, mMainHandler);
         }
     }
 
     @Override
     public boolean isMenuVisible() {
-        return mMenuView != null && mMenuView.isVisible();
+        boolean isVisible = mPipMenuView != null && mPipMenuView.isVisible();
+        if (DEBUG) Log.d(TAG, "isMenuVisible: " + isVisible);
+        return isVisible;
+    }
+
+    /**
+     * Does an immediate window crop of the PiP menu.
+     */
+    @Override
+    public void resizePipMenu(@android.annotation.Nullable SurfaceControl pipLeash,
+            @android.annotation.Nullable SurfaceControl.Transaction t,
+            Rect destinationBounds) {
+        if (DEBUG) Log.d(TAG, "resizePipMenu: " + destinationBounds.toShortString());
+        if (destinationBounds.isEmpty()) {
+            return;
+        }
+
+        if (!maybeCreateSyncApplier()) {
+            return;
+        }
+
+        SurfaceControl surfaceControl = getSurfaceControl();
+        SyncRtSurfaceTransactionApplier.SurfaceParams
+                params = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(surfaceControl)
+                .withWindowCrop(getMenuBounds(destinationBounds))
+                .build();
+        if (pipLeash != null && t != null) {
+            SyncRtSurfaceTransactionApplier.SurfaceParams
+                    pipParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
+                    .withMergeTransaction(t)
+                    .build();
+            mApplier.scheduleApply(params, pipParams);
+        } else {
+            mApplier.scheduleApply(params);
+        }
+    }
+
+    private SurfaceControl getSurfaceControl() {
+        return mSystemWindows.getViewSurface(mPipMenuView);
+    }
+
+    @Override
+    public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction transaction,
+            Rect pipDestBounds) {
+        if (DEBUG) Log.d(TAG, "movePipMenu: " + pipDestBounds.toShortString());
+
+        if (pipDestBounds.isEmpty()) {
+            if (transaction == null && DEBUG) Log.d(TAG, "no transaction given");
+            return;
+        }
+        if (!maybeCreateSyncApplier()) {
+            return;
+        }
+
+        Rect menuDestBounds = getMenuBounds(pipDestBounds);
+        Rect mTmpSourceBounds = new Rect();
+        // If there is no pip leash supplied, that means the PiP leash is already finalized
+        // resizing and the PiP menu is also resized. We then want to do a scale from the current
+        // new menu bounds.
+        if (pipLeash != null && transaction != null) {
+            if (DEBUG) Log.d(TAG, "mTmpSourceBounds based on mPipMenuView.getBoundsOnScreen()");
+            mPipMenuView.getBoundsOnScreen(mTmpSourceBounds);
+        } else {
+            if (DEBUG) Log.d(TAG, "mTmpSourceBounds based on menu width and height");
+            mTmpSourceBounds.set(0, 0, menuDestBounds.width(), menuDestBounds.height());
+        }
+
+        mTmpSourceRectF.set(mTmpSourceBounds);
+        mTmpDestinationRectF.set(menuDestBounds);
+        mMoveTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
+
+        SurfaceControl surfaceControl = getSurfaceControl();
+        SyncRtSurfaceTransactionApplier.SurfaceParams params =
+                new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
+                        surfaceControl).withMatrix(mMoveTransform).build();
+
+        if (pipLeash != null && transaction != null) {
+            SyncRtSurfaceTransactionApplier.SurfaceParams
+                    pipParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
+                    .withMergeTransaction(transaction)
+                    .build();
+            mApplier.scheduleApply(params, pipParams);
+        } else {
+            mApplier.scheduleApply(params);
+        }
+
+        if (mPipMenuView.getViewRootImpl() != null) {
+            mPipMenuView.getHandler().removeCallbacks(mUpdateEmbeddedMatrix);
+            mPipMenuView.getHandler().post(mUpdateEmbeddedMatrix);
+        }
+    }
+
+    private boolean maybeCreateSyncApplier() {
+        if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
+            Log.v(TAG, "Not going to move PiP, either menu or its parent is not created.");
+            return false;
+        }
+
+        if (mApplier == null) {
+            mApplier = new SyncRtSurfaceTransactionApplier(mPipMenuView);
+        }
+        return true;
+    }
+
+    private void detachPipMenuView() {
+        if (mPipMenuView == null) {
+            return;
+        }
+
+        mApplier = null;
+        mSystemWindows.removeView(mPipMenuView);
+        mPipMenuView = null;
+    }
+
+    @Override
+    public void updateMenuBounds(Rect destinationBounds) {
+        Rect menuBounds = getMenuBounds(destinationBounds);
+        if (DEBUG) Log.d(TAG, "updateMenuBounds: " + menuBounds.toShortString());
+        mSystemWindows.updateViewLayout(mPipMenuView,
+                getPipMenuLayoutParams(MENU_WINDOW_TITLE, menuBounds.width(),
+                        menuBounds.height()));
     }
 
     @Override
     public void onBackPress() {
-        hideMenu();
+        if (!onExitMoveMode()) {
+            hideMenu();
+        }
     }
 
     @Override
@@ -240,8 +416,14 @@
     }
 
     interface Delegate {
-        void movePipToNormalPosition();
         void movePipToFullscreen();
+
+        void movePip(int keycode);
+
+        int getPipGravity();
+
+        void closeMenu();
+
         void closePip();
     }
 }
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 4327f15..0141b6a 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
@@ -16,17 +16,21 @@
 
 package com.android.wm.shell.pip.tv;
 
-import static android.animation.AnimatorInflater.loadAnimator;
 import static android.view.KeyEvent.ACTION_UP;
 import static android.view.KeyEvent.KEYCODE_BACK;
+import static android.view.KeyEvent.KEYCODE_DPAD_CENTER;
+import static android.view.KeyEvent.KEYCODE_DPAD_DOWN;
+import static android.view.KeyEvent.KEYCODE_DPAD_LEFT;
+import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
+import static android.view.KeyEvent.KEYCODE_DPAD_UP;
 
-import android.animation.Animator;
 import android.app.PendingIntent;
 import android.app.RemoteAction;
 import android.content.Context;
 import android.os.Handler;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.SurfaceControl;
@@ -34,6 +38,7 @@
 import android.view.ViewRootImpl;
 import android.view.WindowManagerGlobal;
 import android.widget.FrameLayout;
+import android.widget.ImageView;
 import android.widget.LinearLayout;
 
 import androidx.annotation.NonNull;
@@ -45,16 +50,14 @@
 import java.util.List;
 
 /**
- * A View that represents Pip Menu on TV. It's responsible for displaying 2 ever-present Pip Menu
- * actions: Fullscreen and Close, but could also display "additional" actions, that may be set via
- * a {@link #setAdditionalActions(List, Handler)} call.
+ * A View that represents Pip Menu on TV. It's responsible for displaying 3 ever-present Pip Menu
+ * actions: Fullscreen, Move and Close, but could also display "additional" actions, that may be set
+ * via a {@link #setAdditionalActions(List, Handler)} call.
  */
 public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
     private static final String TAG = "TvPipMenuView";
     private static final boolean DEBUG = TvPipController.DEBUG;
 
-    private final Animator mFadeInAnimation;
-    private final Animator mFadeOutAnimation;
     @Nullable
     private Listener mListener;
 
@@ -62,6 +65,11 @@
     private final View mMenuFrameView;
     private final List<TvPipMenuActionButton> mAdditionalButtons = new ArrayList<>();
 
+    private final ImageView mArrowUp;
+    private final ImageView mArrowRight;
+    private final ImageView mArrowDown;
+    private final ImageView mArrowLeft;
+
     public TvPipMenuView(@NonNull Context context) {
         this(context, null);
     }
@@ -85,35 +93,68 @@
                 .setOnClickListener(this);
         mActionButtonsContainer.findViewById(R.id.tv_pip_menu_close_button)
                 .setOnClickListener(this);
+        mActionButtonsContainer.findViewById(R.id.tv_pip_menu_move_button)
+                .setOnClickListener(this);
 
         mMenuFrameView = findViewById(R.id.tv_pip_menu_frame);
-        mFadeInAnimation = loadAnimator(mContext, R.anim.tv_pip_menu_fade_in_animation);
-        mFadeInAnimation.setTarget(mMenuFrameView);
 
-        mFadeOutAnimation = loadAnimator(mContext, R.anim.tv_pip_menu_fade_out_animation);
-        mFadeOutAnimation.setTarget(mMenuFrameView);
+        mArrowUp = findViewById(R.id.tv_pip_menu_arrow_up);
+        mArrowRight = findViewById(R.id.tv_pip_menu_arrow_right);
+        mArrowDown = findViewById(R.id.tv_pip_menu_arrow_down);
+        mArrowLeft = findViewById(R.id.tv_pip_menu_arrow_left);
     }
 
     void setListener(@Nullable Listener listener) {
         mListener = listener;
     }
 
-    void show() {
-        if (DEBUG) Log.d(TAG, "show()");
-
-        mFadeInAnimation.start();
+    void show(boolean inMoveMode, int gravity) {
+        if (DEBUG) Log.d(TAG, "show(), inMoveMode: " + inMoveMode);
         grantWindowFocus(true);
+
+        if (inMoveMode) {
+            showMovementHints(gravity);
+        } else {
+            animateAlphaTo(1, mActionButtonsContainer);
+        }
+        animateAlphaTo(1, mMenuFrameView);
     }
 
-    void hide() {
+    void hide(boolean isInMoveMode) {
         if (DEBUG) Log.d(TAG, "hide()");
+        animateAlphaTo(0, mActionButtonsContainer);
+        animateAlphaTo(0, mMenuFrameView);
+        hideMovementHints();
 
-        mFadeOutAnimation.start();
-        grantWindowFocus(false);
+        if (!isInMoveMode) {
+            grantWindowFocus(false);
+        }
+    }
+
+    private void animateAlphaTo(float alpha, View view) {
+        view.animate()
+                .alpha(alpha)
+                .setInterpolator(alpha == 0f ? TvPipInterpolators.EXIT : TvPipInterpolators.ENTER)
+                .setDuration(500)
+                .withStartAction(() -> {
+                    if (alpha != 0) {
+                        view.setVisibility(VISIBLE);
+                    }
+                })
+                .withEndAction(() -> {
+                    if (alpha == 0) {
+                        view.setVisibility(GONE);
+                    }
+                });
     }
 
     boolean isVisible() {
-        return mMenuFrameView != null && mMenuFrameView.getAlpha() != 0.0f;
+        return mMenuFrameView.getAlpha() != 0f
+                || mActionButtonsContainer.getAlpha() != 0f
+                || mArrowUp.getAlpha() != 0f
+                || mArrowRight.getAlpha() != 0f
+                || mArrowDown.getAlpha() != 0f
+                || mArrowLeft.getAlpha() != 0f;
     }
 
     private void grantWindowFocus(boolean grantFocus) {
@@ -188,6 +229,8 @@
         final int id = v.getId();
         if (id == R.id.tv_pip_menu_fullscreen_button) {
             mListener.onFullscreenButtonClick();
+        } else if (id == R.id.tv_pip_menu_move_button) {
+            mListener.onEnterMoveMode();
         } else if (id == R.id.tv_pip_menu_close_button) {
             mListener.onCloseButtonClick();
         } else {
@@ -207,17 +250,79 @@
 
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
-        if (event.getAction() == ACTION_UP && event.getKeyCode() == KEYCODE_BACK
-                && mListener != null) {
-            mListener.onBackPress();
-            return true;
+        if (DEBUG) {
+            Log.d(TAG, "dispatchKeyEvent, action: " + event.getAction()
+                    + ", keycode: " + event.getKeyCode());
+        }
+        if (mListener != null && event.getAction() == ACTION_UP) {
+            switch (event.getKeyCode()) {
+                case KEYCODE_BACK:
+                    mListener.onBackPress();
+                    return true;
+                case KEYCODE_DPAD_UP:
+                case KEYCODE_DPAD_DOWN:
+                case KEYCODE_DPAD_LEFT:
+                case KEYCODE_DPAD_RIGHT:
+                    return mListener.onPipMovement(event.getKeyCode()) || super.dispatchKeyEvent(
+                            event);
+                case KEYCODE_DPAD_CENTER:
+                    return mListener.onExitMoveMode() || super.dispatchKeyEvent(event);
+                default:
+                    break;
+            }
         }
         return super.dispatchKeyEvent(event);
     }
 
+    /**
+     * Shows user hints for moving the PiP, e.g. arrows.
+     */
+    public void showMovementHints(int gravity) {
+        if (DEBUG) Log.d(TAG, "showMovementHints(), position: " + Gravity.toString(gravity));
+
+        animateAlphaTo((gravity & Gravity.BOTTOM) == Gravity.BOTTOM ? 1f : 0f, mArrowUp);
+        animateAlphaTo((gravity & Gravity.TOP) == Gravity.TOP ? 1f : 0f, mArrowDown);
+        animateAlphaTo((gravity & Gravity.RIGHT) == Gravity.RIGHT ? 1f : 0f, mArrowLeft);
+        animateAlphaTo((gravity & Gravity.LEFT) == Gravity.LEFT ? 1f : 0f, mArrowRight);
+    }
+
+    /**
+     * Hides user hints for moving the PiP, e.g. arrows.
+     */
+    public void hideMovementHints() {
+        if (DEBUG) Log.d(TAG, "hideMovementHints()");
+        animateAlphaTo(0, mArrowUp);
+        animateAlphaTo(0, mArrowRight);
+        animateAlphaTo(0, mArrowDown);
+        animateAlphaTo(0, mArrowLeft);
+    }
+
+    /**
+     * Show or hide the pip user actions.
+     */
+    public void showMenuButtons(boolean show) {
+        if (DEBUG) Log.d(TAG, "showMenuButtons: " + show);
+        animateAlphaTo(show ? 1 : 0, mActionButtonsContainer);
+    }
+
     interface Listener {
+
         void onBackPress();
+
+        void onEnterMoveMode();
+
+        /**
+         * @return whether move mode was exited
+         */
+        boolean onExitMoveMode();
+
+        /**
+         * @return whether pip movement was handled.
+         */
+        boolean onPipMovement(int keycode);
+
         void onCloseButtonClick();
+
         void onFullscreenButtonClick();
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index 551476d..5062cc4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -29,7 +29,6 @@
 
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipMenuController;
 import com.android.wm.shell.pip.PipTransitionController;
@@ -42,11 +41,11 @@
 public class TvPipTransition extends PipTransitionController {
     public TvPipTransition(PipBoundsState pipBoundsState,
             PipMenuController pipMenuController,
-            PipBoundsAlgorithm pipBoundsAlgorithm,
+            TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
             PipAnimationController pipAnimationController,
             Transitions transitions,
             @NonNull ShellTaskOrganizer shellTaskOrganizer) {
-        super(pipBoundsState, pipMenuController, pipBoundsAlgorithm, pipAnimationController,
+        super(pipBoundsState, pipMenuController, tvPipBoundsAlgorithm, pipAnimationController,
                 transitions, shellTaskOrganizer);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 7decb54..338c944 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -292,7 +292,6 @@
          * Invalidates this instance, preventing future calls from updating the controller.
          */
         void invalidate() {
-            Slog.d("b/206648922", "invalidating controller: " + mController);
             mController = null;
         }
 
@@ -313,13 +312,16 @@
         @Override
         public GroupedRecentTaskInfo[] getRecentTasks(int maxNum, int flags, int userId)
                 throws RemoteException {
+            if (mController == null) {
+                // The controller is already invalidated -- just return an empty task list for now
+                return new GroupedRecentTaskInfo[0];
+            }
+
             final GroupedRecentTaskInfo[][] out = new GroupedRecentTaskInfo[][]{null};
             executeRemoteCallWithTaskPermission(mController, "getRecentTasks",
                     (controller) -> out[0] = controller.getRecentTasks(maxNum, flags, userId)
                             .toArray(new GroupedRecentTaskInfo[0]),
                     true /* blocking */);
-            Slog.d("b/206648922", "getRecentTasks(" + maxNum + "): " + out[0]
-                    + " mController=" + mController);
             return out[0];
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
index 08c99b2..8c71f74 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
@@ -35,13 +35,11 @@
  */
 public class CounterRotatorHelper {
     private final ArrayMap<WindowContainerToken, CounterRotator> mRotatorMap = new ArrayMap<>();
-    private SurfaceControl mRootLeash;
 
     /** Puts the surface controls of closing changes to counter-rotated surfaces. */
     public void handleClosingChanges(@NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
             int rotateDelta, int displayW, int displayH) {
-        mRootLeash = info.getRootLeash();
         final List<TransitionInfo.Change> changes = info.getChanges();
         final int numChanges = changes.size();
         for (int i = numChanges - 1; i >= 0; --i) {
@@ -71,10 +69,15 @@
         }
     }
 
-    /** Restores to the original state, i.e. reparent back to transition root. */
-    public void cleanUp() {
+    /**
+     * Removes the counter rotation surface in the finish transaction. No need to reparent the
+     * children as the finish transaction should have already taken care of that.
+     *
+     * This can only be called after startTransaction for {@link #handleClosingChanges} is applied.
+     */
+    public void cleanUp(@NonNull SurfaceControl.Transaction finishTransaction) {
         for (int i = mRotatorMap.size() - 1; i >= 0; --i) {
-            mRotatorMap.valueAt(i).cleanUp(mRootLeash);
+            mRotatorMap.valueAt(i).cleanUp(finishTransaction);
         }
         mRotatorMap.clear();
     }
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 5833ca8..11b2387 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
@@ -282,7 +282,6 @@
         final Runnable onAnimFinish = () -> {
             if (!animations.isEmpty()) return;
 
-            rotator.cleanUp();
             if (mRotationAnimation != null) {
                 mRotationAnimation.kill();
                 mRotationAnimation = null;
@@ -382,6 +381,7 @@
         }
 
         startTransaction.apply();
+        rotator.cleanUp(finishTransaction);
         TransitionMetrics.getInstance().reportAnimationStart(transition);
         // run finish now in-case there are no animations
         onAnimFinish.run();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java
index b9b6716..7f8eaf1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java
@@ -18,14 +18,11 @@
 
 import android.view.SurfaceControl;
 
-import java.util.ArrayList;
-
 /**
  * Utility class that takes care of counter-rotating surfaces during a transition animation.
  */
 public class CounterRotator {
-    SurfaceControl mSurface = null;
-    ArrayList<SurfaceControl> mRotateChildren = null;
+    private SurfaceControl mSurface = null;
 
     /** Gets the surface with the counter-rotation. */
     public SurfaceControl getSurface() {
@@ -41,7 +38,6 @@
     public void setup(SurfaceControl.Transaction t, SurfaceControl parent, int rotateDelta,
             float displayW, float displayH) {
         if (rotateDelta == 0) return;
-        mRotateChildren = new ArrayList<>();
         // We want to counter-rotate, so subtract from 4
         rotateDelta = 4 - (rotateDelta + 4) % 4;
         mSurface = new SurfaceControl.Builder()
@@ -64,24 +60,19 @@
     }
 
     /**
-     * Add a surface that needs to be counter-rotate.
+     * Adds a surface that needs to be counter-rotate.
      */
     public void addChild(SurfaceControl.Transaction t, SurfaceControl child) {
         if (mSurface == null) return;
         t.reparent(child, mSurface);
-        mRotateChildren.add(child);
     }
 
     /**
-     * Clean-up. This undoes any reparenting and effectively stops the counter-rotation.
+     * Clean-up. Since finishTransaction should reset all change leashes, we only need to remove the
+     * counter rotation surface.
      */
-    public void cleanUp(SurfaceControl rootLeash) {
+    public void cleanUp(SurfaceControl.Transaction finishTransaction) {
         if (mSurface == null) return;
-        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-        for (int i = mRotateChildren.size() - 1; i >= 0; --i) {
-            t.reparent(mRotateChildren.get(i), rootLeash);
-        }
-        t.remove(mSurface);
-        t.apply();
+        finishTransaction.remove(mSurface);
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
new file mode 100644
index 0000000..f3f7067
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
@@ -0,0 +1,61 @@
+/**
+ * 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.wm.shell.onehanded;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.testing.TestableLooper;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayLayout;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Tests for {@link BackgroundWindowManager} */
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidJUnit4.class)
+public class BackgroundWindowManagerTest extends ShellTestCase {
+    private BackgroundWindowManager mBackgroundWindowManager;
+    @Mock
+    private DisplayLayout  mMockDisplayLayout;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mBackgroundWindowManager = new BackgroundWindowManager(mContext);
+        mBackgroundWindowManager.onDisplayChanged(mMockDisplayLayout);
+    }
+
+    @Test
+    @UiThreadTest
+    public void testInitRelease() {
+        mBackgroundWindowManager.initView();
+        assertThat(mBackgroundWindowManager.getSurfaceControl()).isNotNull();
+
+        mBackgroundWindowManager.removeBackgroundLayer();
+        assertThat(mBackgroundWindowManager.getSurfaceControl()).isNull();
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java
deleted file mode 100644
index 7b9553c..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizerTest.java
+++ /dev/null
@@ -1,131 +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.wm.shell.onehanded;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL;
-
-import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE;
-import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.Display;
-import android.view.SurfaceControl;
-import android.window.DisplayAreaInfo;
-import android.window.IWindowContainerToken;
-import android.window.WindowContainerToken;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayLayout;
-
-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)
-@TestableLooper.RunWithLooper
-public class OneHandedBackgroundPanelOrganizerTest extends OneHandedTestCase {
-    private DisplayAreaInfo mDisplayAreaInfo;
-    private Display mDisplay;
-    private DisplayLayout mDisplayLayout;
-    private OneHandedBackgroundPanelOrganizer mSpiedBackgroundPanelOrganizer;
-    private WindowContainerToken mToken;
-    private SurfaceControl mLeash;
-
-    @Mock
-    IWindowContainerToken mMockRealToken;
-    @Mock
-    DisplayController mMockDisplayController;
-    @Mock
-    OneHandedSettingsUtil mMockSettingsUtil;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mToken = new WindowContainerToken(mMockRealToken);
-        mLeash = new SurfaceControl();
-        mDisplay = mContext.getDisplay();
-        mDisplayLayout = new DisplayLayout(mContext, mDisplay);
-        when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay);
-        mDisplayAreaInfo = new DisplayAreaInfo(mToken, DEFAULT_DISPLAY,
-                FEATURE_ONE_HANDED_BACKGROUND_PANEL);
-
-        mSpiedBackgroundPanelOrganizer = spy(
-                new OneHandedBackgroundPanelOrganizer(mContext, mDisplayLayout, mMockSettingsUtil,
-                        Runnable::run));
-        mSpiedBackgroundPanelOrganizer.onDisplayChanged(mDisplayLayout);
-    }
-
-    @Test
-    public void testOnDisplayAreaAppeared() {
-        mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
-
-        assertThat(mSpiedBackgroundPanelOrganizer.isRegistered()).isTrue();
-        verify(mSpiedBackgroundPanelOrganizer, never()).showBackgroundPanelLayer();
-    }
-
-    @Test
-    public void testShowBackgroundLayer() {
-        mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, null);
-        mSpiedBackgroundPanelOrganizer.onStart();
-
-        verify(mSpiedBackgroundPanelOrganizer).showBackgroundPanelLayer();
-    }
-
-    @Test
-    public void testRemoveBackgroundLayer() {
-        mSpiedBackgroundPanelOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
-
-        assertThat(mSpiedBackgroundPanelOrganizer.isRegistered()).isNotNull();
-
-        reset(mSpiedBackgroundPanelOrganizer);
-        mSpiedBackgroundPanelOrganizer.removeBackgroundPanelLayer();
-
-        assertThat(mSpiedBackgroundPanelOrganizer.mBackgroundSurface).isNull();
-    }
-
-    @Test
-    public void testStateNone_onConfigurationChanged() {
-        mSpiedBackgroundPanelOrganizer.onStateChanged(STATE_NONE);
-        mSpiedBackgroundPanelOrganizer.onConfigurationChanged();
-
-        verify(mSpiedBackgroundPanelOrganizer, never()).showBackgroundPanelLayer();
-    }
-
-    @Test
-    public void testStateActivate_onConfigurationChanged() {
-        mSpiedBackgroundPanelOrganizer.onStateChanged(STATE_ACTIVE);
-        mSpiedBackgroundPanelOrganizer.onConfigurationChanged();
-
-        verify(mSpiedBackgroundPanelOrganizer).showBackgroundPanelLayer();
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index 636e875..2886b97 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -73,8 +73,6 @@
     @Mock
     DisplayController mMockDisplayController;
     @Mock
-    OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer;
-    @Mock
     OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer;
     @Mock
     OneHandedEventCallback mMockEventCallback;
@@ -115,7 +113,6 @@
         when(mMockDisplayController.getDisplayLayout(anyInt())).thenReturn(null);
         when(mMockDisplayAreaOrganizer.getDisplayAreaTokenMap()).thenReturn(new ArrayMap<>());
         when(mMockDisplayAreaOrganizer.isReady()).thenReturn(true);
-        when(mMockBackgroundOrganizer.isRegistered()).thenReturn(true);
         when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any(), anyInt())).thenReturn(
                 mDefaultEnabled);
         when(mMockSettingsUitl.getSettingsOneHandedModeTimeout(any(), anyInt())).thenReturn(
@@ -134,7 +131,6 @@
         mSpiedOneHandedController = spy(new OneHandedController(
                 mContext,
                 mMockDisplayController,
-                mMockBackgroundOrganizer,
                 mMockDisplayAreaOrganizer,
                 mMockTouchHandler,
                 mMockTutorialHandler,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
index df21163..9c7f723 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
@@ -95,8 +95,6 @@
     @Mock
     WindowContainerTransaction mMockWindowContainerTransaction;
     @Mock
-    OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer;
-    @Mock
     ShellExecutor mMockShellMainExecutor;
     @Mock
     OneHandedSettingsUtil mMockSettingsUitl;
@@ -143,7 +141,6 @@
                 mMockSettingsUitl,
                 mMockAnimationController,
                 mTutorialHandler,
-                mMockBackgroundOrganizer,
                 mJankMonitor,
                 mMockShellMainExecutor));
 
@@ -431,7 +428,6 @@
                         mMockSettingsUitl,
                         mMockAnimationController,
                         mTutorialHandler,
-                        mMockBackgroundOrganizer,
                         mJankMonitor,
                         mMockShellMainExecutor));
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
index 58399b6..dba1b8b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
@@ -67,8 +67,6 @@
     @Mock
     DisplayController mMockDisplayController;
     @Mock
-    OneHandedBackgroundPanelOrganizer mMockBackgroundOrganizer;
-    @Mock
     OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer;
     @Mock
     OneHandedTouchHandler mMockTouchHandler;
@@ -105,7 +103,6 @@
 
         when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay);
         when(mMockDisplayAreaOrganizer.getDisplayAreaTokenMap()).thenReturn(new ArrayMap<>());
-        when(mMockBackgroundOrganizer.isRegistered()).thenReturn(true);
         when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any(), anyInt())).thenReturn(
                 mDefaultEnabled);
         when(mMockSettingsUitl.getSettingsOneHandedModeTimeout(any(), anyInt())).thenReturn(
@@ -123,7 +120,6 @@
         mSpiedOneHandedController = spy(new OneHandedController(
                 mContext,
                 mMockDisplayController,
-                mMockBackgroundOrganizer,
                 mMockDisplayAreaOrganizer,
                 mMockTouchHandler,
                 mMockTutorialHandler,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
index b1434ca..63d8bfd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
@@ -56,6 +56,8 @@
     OneHandedSettingsUtil mMockSettingsUtil;
     @Mock
     WindowManager mMockWindowManager;
+    @Mock
+    BackgroundWindowManager mMockBackgroundWindowManager;
 
     @Before
     public void setUp() {
@@ -63,10 +65,11 @@
         when(mMockSettingsUtil.getTutorialShownCounts(any(), anyInt())).thenReturn(0);
 
         mDisplay = mContext.getDisplay();
-        mDisplayLayout = new DisplayLayout(mContext, mDisplay);
+        mDisplayLayout = new DisplayLayout(getTestContext().getApplicationContext(), mDisplay);
         mSpiedTransitionState = spy(new OneHandedState());
         mSpiedTutorialHandler = spy(
-                new OneHandedTutorialHandler(mContext, mMockSettingsUtil, mMockWindowManager));
+                new OneHandedTutorialHandler(mContext, mMockSettingsUtil, mMockWindowManager,
+                        mMockBackgroundWindowManager));
         mTimeoutHandler = new OneHandedTimeoutHandler(mMockShellMainExecutor);
     }
 
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index b802908..e359145 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -95,6 +95,16 @@
                                         endHyphen, advances);
 }
 
+minikin::MinikinExtent MinikinUtils::getFontExtent(const Paint* paint, minikin::Bidi bidiFlags,
+                                                   const Typeface* typeface, const uint16_t* buf,
+                                                   size_t start, size_t count, size_t bufSize) {
+    minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface);
+    const minikin::U16StringPiece textBuf(buf, bufSize);
+    const minikin::Range range(start, start + count);
+
+    return minikin::getFontExtent(textBuf, range, bidiFlags, minikinPaint);
+}
+
 bool MinikinUtils::hasVariationSelector(const Typeface* typeface, uint32_t codepoint, uint32_t vs) {
     const Typeface* resolvedFace = Typeface::resolveDefault(typeface);
     return resolvedFace->fFontCollection->hasVariationSelector(codepoint, vs);
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index a15803a..009b84b 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -56,6 +56,10 @@
                                          size_t start, size_t count, size_t bufSize,
                                          float* advances);
 
+    static minikin::MinikinExtent getFontExtent(const Paint* paint, minikin::Bidi bidiFlags,
+                                                const Typeface* typeface, const uint16_t* buf,
+                                                size_t start, size_t count, size_t bufSize);
+
     static bool hasVariationSelector(const Typeface* typeface, uint32_t codepoint,
                                                  uint32_t vs);
 
diff --git a/libs/hwui/jni/NinePatch.cpp b/libs/hwui/jni/NinePatch.cpp
index b7ddd21..08fc80f 100644
--- a/libs/hwui/jni/NinePatch.cpp
+++ b/libs/hwui/jni/NinePatch.cpp
@@ -67,7 +67,7 @@
         size_t chunkSize = obj != NULL ? env->GetArrayLength(obj) : 0;
         if (chunkSize < (int) (sizeof(Res_png_9patch))) {
             jniThrowRuntimeException(env, "Array too small for chunk.");
-            return NULL;
+            return 0;
         }
 
         int8_t* storage = new int8_t[chunkSize];
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index 22a1e1f..f768632 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -541,26 +541,6 @@
         return result;
     }
 
-    // ------------------ @FastNative ---------------------------
-
-    static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) {
-        Paint* obj = reinterpret_cast<Paint*>(objHandle);
-        ScopedUtfChars localesChars(env, locales);
-        jint minikinLocaleListId = minikin::registerLocaleList(localesChars.c_str());
-        obj->setMinikinLocaleListId(minikinLocaleListId);
-        return minikinLocaleListId;
-    }
-
-    static void setFontFeatureSettings(JNIEnv* env, jobject clazz, jlong paintHandle, jstring settings) {
-        Paint* paint = reinterpret_cast<Paint*>(paintHandle);
-        if (!settings) {
-            paint->setFontFeatureSettings(std::string());
-        } else {
-            ScopedUtfChars settingsChars(env, settings);
-            paint->setFontFeatureSettings(std::string(settingsChars.c_str(), settingsChars.size()));
-        }
-    }
-
     static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics *metrics) {
         const int kElegantTop = 2500;
         const int kElegantBottom = -1000;
@@ -593,6 +573,67 @@
         return spacing;
     }
 
+    static void doFontExtent(JNIEnv* env, jlong paintHandle, const jchar buf[], jint start,
+                             jint count, jint bufSize, jboolean isRtl, jobject fmi) {
+        const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+        const Typeface* typeface = paint->getAndroidTypeface();
+        minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
+        minikin::MinikinExtent extent =
+                MinikinUtils::getFontExtent(paint, bidiFlags, typeface, buf, start, count, bufSize);
+
+        SkFontMetrics metrics;
+        getMetricsInternal(paintHandle, &metrics);
+
+        metrics.fAscent = extent.ascent;
+        metrics.fDescent = extent.descent;
+
+        // If top/bottom is narrower than ascent/descent, adjust top/bottom to ascent/descent.
+        metrics.fTop = std::min(metrics.fAscent, metrics.fTop);
+        metrics.fBottom = std::max(metrics.fDescent, metrics.fBottom);
+
+        GraphicsJNI::set_metrics_int(env, fmi, metrics);
+    }
+
+    static void getFontMetricsIntForText___C(JNIEnv* env, jclass, jlong paintHandle,
+                                             jcharArray text, jint start, jint count, jint ctxStart,
+                                             jint ctxCount, jboolean isRtl, jobject fmi) {
+        ScopedCharArrayRO textArray(env, text);
+
+        doFontExtent(env, paintHandle, textArray.get() + ctxStart, start - ctxStart, count,
+                     ctxCount, isRtl, fmi);
+    }
+
+    static void getFontMetricsIntForText___String(JNIEnv* env, jclass, jlong paintHandle,
+                                                  jstring text, jint start, jint count,
+                                                  jint ctxStart, jint ctxCount, jboolean isRtl,
+                                                  jobject fmi) {
+        ScopedStringChars textChars(env, text);
+
+        doFontExtent(env, paintHandle, textChars.get() + ctxStart, start - ctxStart, count,
+                     ctxCount, isRtl, fmi);
+    }
+
+    // ------------------ @FastNative ---------------------------
+
+    static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) {
+        Paint* obj = reinterpret_cast<Paint*>(objHandle);
+        ScopedUtfChars localesChars(env, locales);
+        jint minikinLocaleListId = minikin::registerLocaleList(localesChars.c_str());
+        obj->setMinikinLocaleListId(minikinLocaleListId);
+        return minikinLocaleListId;
+    }
+
+    static void setFontFeatureSettings(JNIEnv* env, jobject clazz, jlong paintHandle,
+                                       jstring settings) {
+        Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+        if (!settings) {
+            paint->setFontFeatureSettings(std::string());
+        } else {
+            ScopedUtfChars settingsChars(env, settings);
+            paint->setFontFeatureSettings(std::string(settingsChars.c_str(), settingsChars.size()));
+        }
+    }
+
     static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) {
         SkFontMetrics metrics;
         SkScalar spacing = getMetricsInternal(paintHandle, &metrics);
@@ -1015,6 +1056,11 @@
     {"nGetRunAdvance", "(J[CIIIIZI)F", (void*) PaintGlue::getRunAdvance___CIIIIZI_F},
     {"nGetOffsetForAdvance", "(J[CIIIIZF)I",
             (void*) PaintGlue::getOffsetForAdvance___CIIIIZF_I},
+    {"nGetFontMetricsIntForText", "(J[CIIIIZLandroid/graphics/Paint$FontMetricsInt;)V",
+      (void*)PaintGlue::getFontMetricsIntForText___C},
+    {"nGetFontMetricsIntForText",
+      "(JLjava/lang/String;IIIIZLandroid/graphics/Paint$FontMetricsInt;)V",
+      (void*)PaintGlue::getFontMetricsIntForText___String},
 
     // --------------- @FastNative ----------------------
 
@@ -1093,6 +1139,7 @@
     {"nEqualsForTextMeasurement", "(JJ)Z", (void*)PaintGlue::equalsForTextMeasurement},
 };
 
+
 int register_android_graphics_Paint(JNIEnv* env) {
     return RegisterMethodsOrDie(env, "android/graphics/Paint", methods, NELEM(methods));
 }
diff --git a/libs/hwui/jni/android_util_PathParser.cpp b/libs/hwui/jni/android_util_PathParser.cpp
index 72995ef..8cbb70e 100644
--- a/libs/hwui/jni/android_util_PathParser.cpp
+++ b/libs/hwui/jni/android_util_PathParser.cpp
@@ -61,7 +61,7 @@
     } else {
         delete pathData;
         doThrowIAE(env, result.failureMessage.c_str());
-        return NULL;
+        return 0;
     }
 }
 
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 6755b7c..d862377 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -119,7 +119,7 @@
             AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex(cbData);
     int64_t vsyncId = AChoreographerFrameCallbackData_getFrameTimelineVsyncId(
             cbData, preferredFrameTimelineIndex);
-    int64_t frameDeadline = AChoreographerFrameCallbackData_getFrameTimelineDeadline(
+    int64_t frameDeadline = AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos(
             cbData, preferredFrameTimelineIndex);
     int64_t frameTimeNanos = AChoreographerFrameCallbackData_getFrameTimeNanos(cbData);
     // TODO(b/193273294): Remove when shared memory in use w/ expected present time always current.
diff --git a/location/java/android/location/Geocoder.java b/location/java/android/location/Geocoder.java
index e4a0d0c..a158344 100644
--- a/location/java/android/location/Geocoder.java
+++ b/location/java/android/location/Geocoder.java
@@ -298,6 +298,7 @@
      * @param lowerLeftLongitude  the longitude of the lower left corner of the bounding box
      * @param upperRightLatitude  the latitude of the upper right corner of the bounding box
      * @param upperRightLongitude the longitude of the upper right corner of the bounding box
+     * @param listener            a listener for receiving results
      *
      * @throws IllegalArgumentException if locationName is null
      * @throws IllegalArgumentException if any latitude or longitude is invalid
diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java
index 2c94820d..ecdd4b6 100644
--- a/location/java/android/location/GnssMeasurement.java
+++ b/location/java/android/location/GnssMeasurement.java
@@ -1860,7 +1860,7 @@
             gnssMeasurement.mSatelliteInterSignalBiasUncertaintyNanos = parcel.readDouble();
             if (gnssMeasurement.hasSatellitePvt()) {
                 ClassLoader classLoader = getClass().getClassLoader();
-                gnssMeasurement.mSatellitePvt = parcel.readParcelable(classLoader, android.location.SatellitePvt.class);
+                gnssMeasurement.mSatellitePvt = parcel.readParcelable(classLoader);
             }
             if (gnssMeasurement.hasCorrelationVectors()) {
                 CorrelationVector[] correlationVectorsArray =
diff --git a/location/java/android/location/GnssMeasurementsEvent.java b/location/java/android/location/GnssMeasurementsEvent.java
index b744017..a07a64a 100644
--- a/location/java/android/location/GnssMeasurementsEvent.java
+++ b/location/java/android/location/GnssMeasurementsEvent.java
@@ -156,7 +156,7 @@
         public GnssMeasurementsEvent createFromParcel(Parcel in) {
             ClassLoader classLoader = getClass().getClassLoader();
 
-            GnssClock clock = in.readParcelable(classLoader, android.location.GnssClock.class);
+            GnssClock clock = in.readParcelable(classLoader);
 
             int measurementsLength = in.readInt();
             GnssMeasurement[] measurementsArray = new GnssMeasurement[measurementsLength];
diff --git a/location/java/android/location/GpsMeasurementsEvent.java b/location/java/android/location/GpsMeasurementsEvent.java
index 6b834f3..f3feb7a 100644
--- a/location/java/android/location/GpsMeasurementsEvent.java
+++ b/location/java/android/location/GpsMeasurementsEvent.java
@@ -112,7 +112,7 @@
         public GpsMeasurementsEvent createFromParcel(Parcel in) {
             ClassLoader classLoader = getClass().getClassLoader();
 
-            GpsClock clock = in.readParcelable(classLoader, android.location.GpsClock.class);
+            GpsClock clock = in.readParcelable(classLoader);
 
             int measurementsLength = in.readInt();
             GpsMeasurement[] measurementsArray = new GpsMeasurement[measurementsLength];
diff --git a/location/java/android/location/GpsNavigationMessageEvent.java b/location/java/android/location/GpsNavigationMessageEvent.java
index b37fe3d..2d5d6eb 100644
--- a/location/java/android/location/GpsNavigationMessageEvent.java
+++ b/location/java/android/location/GpsNavigationMessageEvent.java
@@ -92,7 +92,7 @@
                 @Override
                 public GpsNavigationMessageEvent createFromParcel(Parcel in) {
                     ClassLoader classLoader = getClass().getClassLoader();
-                    GpsNavigationMessage navigationMessage = in.readParcelable(classLoader, android.location.GpsNavigationMessage.class);
+                    GpsNavigationMessage navigationMessage = in.readParcelable(classLoader);
                     return new GpsNavigationMessageEvent(navigationMessage);
                 }
 
diff --git a/location/java/android/location/SatellitePvt.java b/location/java/android/location/SatellitePvt.java
index aa43cfd..794a8d0 100644
--- a/location/java/android/location/SatellitePvt.java
+++ b/location/java/android/location/SatellitePvt.java
@@ -465,9 +465,9 @@
                 public SatellitePvt createFromParcel(Parcel in) {
                     int flags = in.readInt();
                     ClassLoader classLoader = getClass().getClassLoader();
-                    PositionEcef positionEcef = in.readParcelable(classLoader, android.location.SatellitePvt.PositionEcef.class);
-                    VelocityEcef velocityEcef = in.readParcelable(classLoader, android.location.SatellitePvt.VelocityEcef.class);
-                    ClockInfo clockInfo = in.readParcelable(classLoader, android.location.SatellitePvt.ClockInfo.class);
+                    PositionEcef positionEcef = in.readParcelable(classLoader);
+                    VelocityEcef velocityEcef = in.readParcelable(classLoader);
+                    ClockInfo clockInfo = in.readParcelable(classLoader);
                     double ionoDelayMeters = in.readDouble();
                     double tropoDelayMeters = in.readDouble();
 
diff --git a/media/OWNERS b/media/OWNERS
index 0aff43e..5f50137 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -3,7 +3,6 @@
 essick@google.com
 etalvala@google.com
 hdmoon@google.com
-hkuang@google.com
 hunga@google.com
 insun@google.com
 jaewan@google.com
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 21fc6ec..ffd5eaa 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2995,19 +2995,17 @@
         void onModeChanged(@AudioMode int mode);
     }
 
-    private final Object mModeListenerLock = new Object();
     /**
-     * List of listeners for audio mode and their associated Executor.
-     * List is lazy-initialized on first registration
+     * manages the OnModeChangedListener listeners and the ModeDispatcherStub
      */
-    @GuardedBy("mModeListenerLock")
-    private @Nullable ArrayList<ListenerInfo<OnModeChangedListener>> mModeListeners;
+    private final CallbackUtil.LazyListenerManager<OnModeChangedListener> mModeChangedListenerMgr =
+            new CallbackUtil.LazyListenerManager();
 
-    @GuardedBy("mModeListenerLock")
-    private ModeDispatcherStub mModeDispatcherStub;
 
-    private final class ModeDispatcherStub extends IAudioModeDispatcher.Stub {
+    final class ModeDispatcherStub extends IAudioModeDispatcher.Stub
+            implements CallbackUtil.DispatcherStub {
 
+        @Override
         public void register(boolean register) {
             try {
                 if (register) {
@@ -3021,10 +3019,8 @@
         }
 
         @Override
-        @SuppressLint("GuardedBy") // lock applied inside callListeners method
         public void dispatchAudioModeChanged(int mode) {
-            CallbackUtil.callListeners(mModeListeners, mModeListenerLock,
-                    (listener) -> listener.onModeChanged(mode));
+            mModeChangedListenerMgr.callListeners((listener) -> listener.onModeChanged(mode));
         }
     }
 
@@ -3037,15 +3033,8 @@
     public void addOnModeChangedListener(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull OnModeChangedListener listener) {
-        synchronized (mModeListenerLock) {
-            final Pair<ArrayList<ListenerInfo<OnModeChangedListener>>, ModeDispatcherStub> res =
-                    CallbackUtil.addListener("addOnModeChangedListener",
-                            executor, listener, mModeListeners, mModeDispatcherStub,
-                            () -> new ModeDispatcherStub(),
-                            stub -> stub.register(true));
-            mModeListeners = res.first;
-            mModeDispatcherStub = res.second;
-        }
+        mModeChangedListenerMgr.addListener(executor, listener, "addOnModeChangedListener",
+                () -> new ModeDispatcherStub());
     }
 
     /**
@@ -3054,14 +3043,7 @@
      * @param listener
      */
     public void removeOnModeChangedListener(@NonNull OnModeChangedListener listener) {
-        synchronized (mModeListenerLock) {
-            final Pair<ArrayList<ListenerInfo<OnModeChangedListener>>, ModeDispatcherStub> res =
-                    CallbackUtil.removeListener("removeOnModeChangedListener",
-                            listener, mModeListeners, mModeDispatcherStub,
-                            stub -> stub.register(false));
-            mModeListeners = res.first;
-            mModeDispatcherStub = res.second;
-        }
+        mModeChangedListenerMgr.removeListener(listener, "removeOnModeChangedListener");
     }
 
     /**
@@ -7718,6 +7700,12 @@
     }
 
     /**
+     * manages the OnCommunicationDeviceChangedListener listeners and the
+     * CommunicationDeviceDispatcherStub
+     */
+    private final CallbackUtil.LazyListenerManager<OnCommunicationDeviceChangedListener>
+            mCommDeviceChangedListenerMgr = new CallbackUtil.LazyListenerManager();
+    /**
      * Adds a listener for being notified of changes to the communication audio device.
      * See {@link #setCommunicationDevice(AudioDeviceInfo)}.
      * @param executor
@@ -7726,16 +7714,9 @@
     public void addOnCommunicationDeviceChangedListener(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull OnCommunicationDeviceChangedListener listener) {
-        synchronized (mCommDevListenerLock) {
-            final Pair<ArrayList<ListenerInfo<OnCommunicationDeviceChangedListener>>,
-                    CommunicationDeviceDispatcherStub> res =
-                    CallbackUtil.addListener("addOnCommunicationDeviceChangedListener",
-                            executor, listener, mCommDevListeners, mCommDevDispatcherStub,
-                            () -> new CommunicationDeviceDispatcherStub(),
-                            stub -> stub.register(true));
-            mCommDevListeners = res.first;
-            mCommDevDispatcherStub = res.second;
-        }
+        mCommDeviceChangedListenerMgr.addListener(
+                            executor, listener, "addOnCommunicationDeviceChangedListener",
+                            () -> new CommunicationDeviceDispatcherStub());
     }
 
     /**
@@ -7745,32 +7726,14 @@
      */
     public void removeOnCommunicationDeviceChangedListener(
             @NonNull OnCommunicationDeviceChangedListener listener) {
-        synchronized (mCommDevListenerLock) {
-            final Pair<ArrayList<ListenerInfo<OnCommunicationDeviceChangedListener>>,
-                    CommunicationDeviceDispatcherStub> res =
-                    CallbackUtil.removeListener("removeOnCommunicationDeviceChangedListener",
-                            listener, mCommDevListeners, mCommDevDispatcherStub,
-                            stub -> stub.register(false));
-            mCommDevListeners = res.first;
-            mCommDevDispatcherStub = res.second;
-        }
+        mCommDeviceChangedListenerMgr.removeListener(listener,
+                "removeOnCommunicationDeviceChangedListener");
     }
 
-    private final Object mCommDevListenerLock = new Object();
-    /**
-     * List of listeners for preferred device for strategy and their associated Executor.
-     * List is lazy-initialized on first registration
-     */
-    @GuardedBy("mCommDevListenerLock")
-    private @Nullable
-            ArrayList<ListenerInfo<OnCommunicationDeviceChangedListener>> mCommDevListeners;
-
-    @GuardedBy("mCommDevListenerLock")
-    private CommunicationDeviceDispatcherStub mCommDevDispatcherStub;
-
     private final class CommunicationDeviceDispatcherStub
-            extends ICommunicationDeviceDispatcher.Stub {
+            extends ICommunicationDeviceDispatcher.Stub implements CallbackUtil.DispatcherStub {
 
+        @Override
         public void register(boolean register) {
             try {
                 if (register) {
@@ -7784,10 +7747,9 @@
         }
 
         @Override
-        @SuppressLint("GuardedBy") // lock applied inside callListeners method
         public void dispatchCommunicationDeviceChanged(int portId) {
             AudioDeviceInfo device = getDeviceForPortId(portId, GET_DEVICES_OUTPUTS);
-            CallbackUtil.callListeners(mCommDevListeners, mCommDevListenerLock,
+            mCommDeviceChangedListenerMgr.callListeners(
                     (listener) -> listener.onCommunicationDeviceChanged(device));
         }
     }
diff --git a/media/java/android/media/CallbackUtil.java b/media/java/android/media/CallbackUtil.java
index ac39317..2b5fd25 100644
--- a/media/java/android/media/CallbackUtil.java
+++ b/media/java/android/media/CallbackUtil.java
@@ -18,11 +18,14 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.media.permission.ClearCallingIdentityContext;
 import android.media.permission.SafeCloseable;
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.ArrayList;
 import java.util.Objects;
 import java.util.concurrent.Executor;
@@ -221,4 +224,87 @@
         }
 
     }
+
+    /**
+     * Interface to be implemented by stub implementation for the events received from a server
+     * to the class managing the listener API.
+     * For an example see {@link AudioManager#ModeDispatcherStub} which registers with AudioService.
+     */
+    interface DispatcherStub {
+        /**
+         * Register/unregister the stub as a listener of the events to be forwarded to the listeners
+         * managed by LazyListenerManager.
+         * @param register true for registering, false to unregister
+         */
+        void register(boolean register);
+    }
+
+    /**
+     * Class to manage a list of listeners and their callback, and the associated stub which
+     * receives the events to be forwarded to the listeners.
+     * The list of listeners and the stub and its registration are lazily initialized and registered
+     * @param <T> the listener class
+     */
+    static class LazyListenerManager<T> {
+        private final Object mListenerLock = new Object();
+
+        @GuardedBy("mListenerLock")
+        private @Nullable ArrayList<ListenerInfo<T>> mListeners;
+
+        @GuardedBy("mListenerLock")
+        private @Nullable DispatcherStub mDispatcherStub;
+
+        LazyListenerManager() {
+            // nothing to initialize as instances of dispatcher and list of listeners
+            // are lazily initialized
+        }
+
+        /**
+         * Add a new listener / executor pair for the configured listener
+         * @param executor Executor for the callback
+         * @param listener the listener to register
+         * @param methodName the name of the method calling this utility method for easier to read
+         *          exception messages
+         * @param newStub how to build a new instance of the stub receiving the events when the
+         *          number of listeners goes from 0 to 1, not called until then.
+         */
+        void addListener(@NonNull Executor executor, @NonNull T listener, String methodName,
+                @NonNull java.util.function.Supplier<DispatcherStub> newStub) {
+            synchronized (mListenerLock) {
+                final Pair<ArrayList<ListenerInfo<T>>, DispatcherStub> res =
+                        CallbackUtil.addListener(methodName,
+                                executor, listener, mListeners, mDispatcherStub,
+                                newStub,
+                                stub -> stub.register(true));
+                mListeners = res.first;
+                mDispatcherStub = res.second;
+            }
+        }
+
+        /**
+         * Remove a previously registered listener
+         * @param listener the listener to unregister
+         * @param methodName the name of the method calling this utility method for easier to read
+         *          exception messages
+         */
+        void removeListener(@NonNull T listener, String methodName) {
+            synchronized (mListenerLock) {
+                final Pair<ArrayList<ListenerInfo<T>>, DispatcherStub> res =
+                        CallbackUtil.removeListener(methodName,
+                                listener, mListeners, mDispatcherStub,
+                                stub -> stub.register(false));
+                mListeners = res.first;
+                mDispatcherStub = res.second;
+            }
+        }
+
+        /**
+         * Call the registered listeners with the given callback method
+         * @param callback the listener method to invoke
+         */
+        @SuppressLint("GuardedBy") // lock applied inside callListeners method
+        void callListeners(CallbackMethod<T> callback) {
+            CallbackUtil.callListeners(mListeners, mListenerLock, callback);
+        }
+    }
 }
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index bd0f32e..69ce8d2 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -18,10 +18,13 @@
 
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.graphics.GraphicBuffer;
 import android.graphics.ImageFormat;
 import android.graphics.ImageFormat.Format;
 import android.graphics.Rect;
+import android.hardware.DataSpace;
+import android.hardware.DataSpace.NamedDataSpace;
 import android.hardware.HardwareBuffer;
 import android.hardware.HardwareBuffer.Usage;
 import android.hardware.camera2.MultiResolutionImageReader;
@@ -136,8 +139,7 @@
         // If the format is private don't default to USAGE_CPU_READ_OFTEN since it may not
         // work, and is inscrutable anyway
         return new ImageReader(width, height, format, maxImages,
-                format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN,
-                /*parent*/ null);
+                format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN, null);
     }
 
     /**
@@ -268,44 +270,32 @@
         // If the format is private don't default to USAGE_CPU_READ_OFTEN since it may not
         // work, and is inscrutable anyway
         return new ImageReader(width, height, format, maxImages,
-                format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN,
-                parent);
+                format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN, parent);
     }
 
-
-    /**
-     * @hide
-     */
-    protected ImageReader(int width, int height, int format, int maxImages, long usage,
-            MultiResolutionImageReader parent) {
-        mWidth = width;
-        mHeight = height;
-        mFormat = format;
-        mUsage = usage;
-        mMaxImages = maxImages;
-        mParent = parent;
-
+    private void initializeImageReader(int width, int height, int imageFormat, int maxImages,
+            long usage, int hardwareBufferFormat, long dataSpace, boolean useLegacyImageFormat) {
         if (width < 1 || height < 1) {
             throw new IllegalArgumentException(
                 "The image dimensions must be positive");
         }
-        if (mMaxImages < 1) {
+
+        if (maxImages < 1) {
             throw new IllegalArgumentException(
                 "Maximum outstanding image count must be at least 1");
         }
 
-        if (format == ImageFormat.NV21) {
+        if (imageFormat == ImageFormat.NV21) {
             throw new IllegalArgumentException(
-                    "NV21 format is not supported");
+                "NV21 format is not supported");
         }
 
-        mNumPlanes = ImageUtils.getNumPlanesForFormat(mFormat);
-
-        nativeInit(new WeakReference<>(this), width, height, format, maxImages, usage);
-
-        mSurface = nativeGetSurface();
+        nativeInit(new WeakReference<>(this), width, height, maxImages, usage,
+                hardwareBufferFormat, dataSpace);
 
         mIsReaderValid = true;
+
+        mSurface = nativeGetSurface();
         // Estimate the native buffer allocation size and register it so it gets accounted for
         // during GC. Note that this doesn't include the buffers required by the buffer queue
         // itself and the buffers requested by the producer.
@@ -313,10 +303,46 @@
         // complex, and 1 buffer is enough for the VM to treat the ImageReader as being of some
         // size.
         mEstimatedNativeAllocBytes = ImageUtils.getEstimatedNativeAllocBytes(
-                width, height, format, /*buffer count*/ 1);
+            width, height, useLegacyImageFormat ? imageFormat : hardwareBufferFormat,
+            /*buffer count*/ 1);
         VMRuntime.getRuntime().registerNativeAllocation(mEstimatedNativeAllocBytes);
     }
 
+    private ImageReader(int width, int height, int imageFormat, int maxImages, long usage,
+            MultiResolutionImageReader parent) {
+        mWidth = width;
+        mHeight = height;
+        mFormat = imageFormat;
+        mUsage = usage;
+        mMaxImages = maxImages;
+        mParent = parent;
+        // retrieve hal Format and hal dataspace from imageFormat
+        mHardwareBufferFormat = PublicFormatUtils.getHalFormat(mFormat);
+        mDataSpace = PublicFormatUtils.getHalDataspace(mFormat);
+        mUseLegacyImageFormat = true;
+        mNumPlanes = ImageUtils.getNumPlanesForFormat(mFormat);
+
+        initializeImageReader(width, height, imageFormat, maxImages, usage, mHardwareBufferFormat,
+                mDataSpace, mUseLegacyImageFormat);
+    }
+
+    private ImageReader(int width, int height, int maxImages, long usage,
+            MultiResolutionImageReader parent, int hardwareBufferFormat, long dataSpace) {
+        mWidth = width;
+        mHeight = height;
+        mFormat = ImageFormat.UNKNOWN; // set default image format value as UNKNOWN
+        mUsage = usage;
+        mMaxImages = maxImages;
+        mParent = parent;
+        mHardwareBufferFormat = hardwareBufferFormat;
+        mDataSpace = dataSpace;
+        mUseLegacyImageFormat = false;
+        mNumPlanes = ImageUtils.getNumPlanesForHardwareBufferFormat(mHardwareBufferFormat);
+
+        initializeImageReader(width, height, mFormat, maxImages, usage, hardwareBufferFormat,
+                dataSpace, mUseLegacyImageFormat);
+    }
+
     /**
      * The default width of {@link Image Images}, in pixels.
      *
@@ -354,6 +380,10 @@
      * As of now, each format is only compatible to itself.
      * The actual format of the images can be found using {@link Image#getFormat}.</p>
      *
+     * <p>Use this function if the ImageReader instance is created by factory method
+     * {@code newInstance} function or by builder pattern {@code ImageReader.Builder} and using
+     * {@link Builder#setImageFormat}.</p>
+     *
      * @return the expected format of an Image
      *
      * @see ImageFormat
@@ -363,6 +393,32 @@
     }
 
     /**
+     * The default {@link HardwareBuffer} format of {@link Image Images}.
+     *
+     * <p>Use this function if the ImageReader instance is created by builder pattern
+     * {@code ImageReader.Builder} and using {@link Builder#setDefaultHardwareBufferFormat} and
+     * {@link Builder#setDefaultDataSpace}.</p>
+     *
+     * @return the expected {@link HardwareBuffer} format of an Image.
+     */
+    public @HardwareBuffer.Format int getHardwareBufferFormat() {
+        return mHardwareBufferFormat;
+    }
+
+    /**
+     * The default dataspace of {@link Image Images}.
+     *
+     * <p>Use this function if the ImageReader instance is created by builder pattern
+     * {@code ImageReader.Builder} and {@link Builder#setDefaultDataSpace}.</p>
+     *
+     * @return the expected dataspace of an Image.
+     */
+    @SuppressLint("MethodNameUnits")
+    public @NamedDataSpace long getDataSpace() {
+        return mDataSpace;
+    }
+
+    /**
      * Maximum number of images that can be acquired from the ImageReader by any time (for example,
      * with {@link #acquireNextImage}).
      *
@@ -384,6 +440,15 @@
     }
 
     /**
+     * The usage flag of images that can be produced by the ImageReader.
+     *
+     * @return The usage flag of the images for this ImageReader.
+     */
+    public @Usage long getUsage() {
+        return mUsage;
+    }
+
+    /**
      * <p>Get a {@link Surface} that can be used to produce {@link Image Images} for this
      * {@code ImageReader}.</p>
      *
@@ -469,7 +534,12 @@
      * @hide
      */
     public Image acquireNextImageNoThrowISE() {
-        SurfaceImage si = new SurfaceImage(mFormat);
+        SurfaceImage si;
+        if (mUseLegacyImageFormat) {
+            si = new SurfaceImage(mFormat);
+        } else {
+            si = new SurfaceImage(mHardwareBufferFormat, mDataSpace);
+        }
         return acquireNextSurfaceImage(si) == ACQUIRE_SUCCESS ? si : null;
     }
 
@@ -492,7 +562,7 @@
             // A null image will eventually be returned if ImageReader is already closed.
             int status = ACQUIRE_NO_BUFS;
             if (mIsReaderValid) {
-                status = nativeImageSetup(si);
+                status = nativeImageSetup(si, mUseLegacyImageFormat);
             }
 
             switch (status) {
@@ -545,7 +615,12 @@
     public Image acquireNextImage() {
         // Initialize with reader format, but can be overwritten by native if the image
         // format is different from the reader format.
-        SurfaceImage si = new SurfaceImage(mFormat);
+        SurfaceImage si;
+        if (mUseLegacyImageFormat) {
+            si = new SurfaceImage(mFormat);
+        } else {
+            si = new SurfaceImage(mHardwareBufferFormat, mDataSpace);
+        }
         int status = acquireNextSurfaceImage(si);
 
         switch (status) {
@@ -838,13 +913,161 @@
         }
     }
 
+    /**
+     * Builder class for {@link ImageReader} objects.
+     */
+    public static final class Builder {
+        private int mWidth;
+        private int mHeight;
+        private int mMaxImages = 1;
+        private int mImageFormat = ImageFormat.UNKNOWN;
+        private int mHardwareBufferFormat = HardwareBuffer.RGBA_8888;
+        private long mDataSpace = DataSpace.DATASPACE_UNKNOWN;
+        private long mUsage = HardwareBuffer.USAGE_CPU_READ_OFTEN;
+        private boolean mUseLegacyImageFormat = false;
+
+        /**
+         * Constructs a new builder for {@link ImageReader}.
+         *
+         * @param width The default width in pixels that will be passed to the producer.
+         *              May be overridden by the producer.
+         * @param height The default height in pixels that will be passed to the producer.
+         *              May be overridden by the producer.
+         * @see Image
+         */
+        public Builder(@IntRange(from = 1) int width, @IntRange(from = 1) int height) {
+            mWidth = width;
+            mHeight = height;
+        }
+
+        /**
+         * Set the maximal number of images.
+         *
+         * @param maxImages The maximum number of images the user will want to
+         *            access simultaneously. This should be as small as possible to
+         *            limit memory use. Default value is 1.
+         * @return the Builder instance with customized usage value.
+         */
+        public @NonNull Builder setMaxImages(int maxImages) {
+            mMaxImages = maxImages;
+            return this;
+        }
+
+        /**
+         * Set the consumer usage flag.
+         *
+         * @param usage The intended usage of the images consumed by this ImageReader.
+         *              See the usages on {@link HardwareBuffer} for a list of valid usage bits.
+         *              Default value is {@link HardwareBuffer#USAGE_CPU_READ_OFTEN}.
+         * @return the Builder instance with customized usage value.
+         *
+         * @see HardwareBuffer
+         */
+        public @NonNull Builder setUsage(long usage) {
+            mUsage = usage;
+            return this;
+        }
+
+        /**
+         * Set the default image format passed by the producer. May be overridden by the producer.
+         *
+         * <p>{@link #setImageFormat} function replaces the combination of
+         * {@link #setDefaultHardwareBufferFormat} and {@link #setDefaultDataSpace} functions.
+         * Either this or these two functions must be called to initialize an {@code ImageReader}
+         * instance.</p>
+         *
+         * @param imageFormat The format of the image that this reader will produce. This
+         *                    must be one of the {@link android.graphics.ImageFormat} or
+         *                   {@link android.graphics.PixelFormat} constants. Note that not
+         *                   all formats are supported, like ImageFormat.NV21. The default value is
+         *                   {@link ImageFormat#UNKNOWN}.
+         * @return the builder instance with customized image format value.
+         *
+         * @see #setDefaultHardwareBufferFormat
+         * @see #setDefaultDataSpace
+         */
+        public @NonNull Builder setImageFormat(@Format int imageFormat) {
+            mImageFormat = imageFormat;
+            mUseLegacyImageFormat = true;
+            mHardwareBufferFormat = HardwareBuffer.RGBA_8888;
+            mDataSpace = DataSpace.DATASPACE_UNKNOWN;
+            return this;
+        }
+
+        /**
+         * Set the default hardwareBuffer format passed by the producer.
+         * May be overridden by the producer.
+         *
+         * <p>This function works together with {@link #setDefaultDataSpace} for an
+         * {@link ImageReader} instance. Setting at least one of these two replaces
+         * {@link #setImageFormat} function.</p>
+         *
+         * <p>The format of the Image can be overridden after {@link #setImageFormat} by calling
+         * this function and then {@link #setDefaultDataSpace} functions.
+         * <i>Warning:</i> Missing one of callings for initializing or overriding the format may
+         * involve undefined behaviors.</p>
+         *
+         * @param hardwareBufferFormat The HardwareBuffer format of the image that this reader
+         *                             will produce. The default value is
+         *                             {@link HardwareBuffer#RGBA_8888 HardwareBuffer.RGBA_8888}.
+         * @return the builder instance with customized hardwareBuffer value.
+         *
+         * @see #setDefaultDataSpace
+         * @see #setImageFormat
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public @NonNull Builder setDefaultHardwareBufferFormat(
+                @HardwareBuffer.Format int hardwareBufferFormat) {
+            mHardwareBufferFormat = hardwareBufferFormat;
+            mUseLegacyImageFormat = false;
+            mImageFormat = ImageFormat.UNKNOWN;
+            return this;
+        }
+
+        /**
+         * Set the default dataspace passed by the producer.
+         * May be overridden by the producer.
+         *
+         * <p>This function works together with {@link #setDefaultHardwareBufferFormat} for an
+         * {@link ImageReader} instance. Setting at least one of these two replaces
+         * {@link #setImageFormat} function.</p>
+         *
+         * @param dataSpace The dataspace of the image that this reader will produce.
+         *                  The default value is {@link DataSpace#DATASPACE_UNKNOWN}.
+         * @return the builder instance with customized dataspace value.
+         *
+         * @see #setDefaultHardwareBufferFormat
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public @NonNull Builder setDefaultDataSpace(@NamedDataSpace long dataSpace) {
+            mDataSpace = dataSpace;
+            mUseLegacyImageFormat = false;
+            mImageFormat = ImageFormat.UNKNOWN;
+            return this;
+        }
+
+        /**
+         * Builds a new ImageReader object.
+         *
+         * @return The new ImageReader object.
+         */
+        public @NonNull ImageReader build() {
+            if (mUseLegacyImageFormat) {
+                return new ImageReader(mWidth, mHeight, mImageFormat, mMaxImages, mUsage, null);
+            } else {
+                return new ImageReader(mWidth, mHeight, mMaxImages, mUsage, null,
+                    mHardwareBufferFormat, mDataSpace);
+            }
+        }
+    }
+
     private final int mWidth;
     private final int mHeight;
     private final int mFormat;
     private final long mUsage;
     private final int mMaxImages;
     private final int mNumPlanes;
-    private final Surface mSurface;
+    private Surface mSurface;
     private int mEstimatedNativeAllocBytes;
 
     private final Object mListenerLock = new Object();
@@ -861,6 +1084,12 @@
     // MultiResolutionImageReader.
     private final MultiResolutionImageReader mParent;
 
+    private final int mHardwareBufferFormat;
+
+    private final long mDataSpace;
+
+    private final boolean mUseLegacyImageFormat;
+
     /**
      * This field is used by native code, do not access or modify.
      */
@@ -897,6 +1126,12 @@
             mFormat = format;
         }
 
+        SurfaceImage(int hardwareBufferFormat, long dataSpace) {
+            mHardwareBufferFormat = hardwareBufferFormat;
+            mDataSpace = dataSpace;
+            mFormat = PublicFormatUtils.getPublicFormat(mHardwareBufferFormat, mDataSpace);
+        }
+
         @Override
         public void close() {
             ImageReader.this.releaseImage(this);
@@ -909,10 +1144,15 @@
         @Override
         public int getFormat() {
             throwISEIfImageIsInvalid();
-            int readerFormat = ImageReader.this.getImageFormat();
-            // Assume opaque reader always produce opaque images.
-            mFormat = (readerFormat == ImageFormat.PRIVATE) ? readerFormat :
-                nativeGetFormat(readerFormat);
+            // update mFormat only if ImageReader is initialized by factory pattern.
+            // if using builder pattern, mFormat has been updated upon initialization.
+            // no need update here.
+            if (ImageReader.this.mUseLegacyImageFormat) {
+                int readerFormat = ImageReader.this.getImageFormat();
+                // Assume opaque reader always produce opaque images.
+                mFormat = (readerFormat == ImageFormat.PRIVATE) ? readerFormat :
+                    nativeGetFormat(readerFormat);
+            }
             return mFormat;
         }
 
@@ -1125,6 +1365,8 @@
 
         private SurfacePlane[] mPlanes;
         private int mFormat = ImageFormat.UNKNOWN;
+        private int mHardwareBufferFormat = HardwareBuffer.RGBA_8888;
+        private long mDataSpace = DataSpace.DATASPACE_UNKNOWN;
         // If this image is detached from the ImageReader.
         private AtomicBoolean mIsDetached = new AtomicBoolean(false);
 
@@ -1137,8 +1379,8 @@
         private synchronized native HardwareBuffer nativeGetHardwareBuffer();
     }
 
-    private synchronized native void nativeInit(Object weakSelf, int w, int h,
-                                                    int fmt, int maxImgs, long consumerUsage);
+    private synchronized native void nativeInit(Object weakSelf, int w, int h, int maxImgs,
+            long consumerUsage, int hardwareBufferFormat, long dataSpace);
     private synchronized native void nativeClose();
     private synchronized native void nativeReleaseImage(Image i);
     private synchronized native Surface nativeGetSurface();
@@ -1152,7 +1394,7 @@
      * @see #ACQUIRE_NO_BUFS
      * @see #ACQUIRE_MAX_IMAGES
      */
-    private synchronized native int nativeImageSetup(Image i);
+    private synchronized native int nativeImageSetup(Image i, boolean legacyValidateImageFormat);
 
     /**
      * @hide
diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java
index 7837d7e..2f1a36c 100644
--- a/media/java/android/media/ImageUtils.java
+++ b/media/java/android/media/ImageUtils.java
@@ -18,6 +18,7 @@
 
 import android.graphics.ImageFormat;
 import android.graphics.PixelFormat;
+import android.hardware.HardwareBuffer;
 import android.media.Image.Plane;
 import android.util.Size;
 
@@ -77,6 +78,34 @@
     }
 
     /**
+     * Only a subset of the formats defined in
+     * {@link android.graphics.HardwareBuffer.Format} constants are supported by ImageReader.
+     */
+    public static int getNumPlanesForHardwareBufferFormat(int hardwareBufferFormat) {
+        switch(hardwareBufferFormat) {
+            case HardwareBuffer.YCBCR_420_888:
+                return 3;
+            case HardwareBuffer.RGBA_8888:
+            case HardwareBuffer.RGBX_8888:
+            case HardwareBuffer.RGB_888:
+            case HardwareBuffer.RGB_565:
+            case HardwareBuffer.RGBA_FP16:
+            case HardwareBuffer.RGBA_1010102:
+            case HardwareBuffer.BLOB:
+            case HardwareBuffer.D_16:
+            case HardwareBuffer.D_24:
+            case HardwareBuffer.DS_24UI8:
+            case HardwareBuffer.D_FP32:
+            case HardwareBuffer.DS_FP32UI8:
+            case HardwareBuffer.S_UI8:
+                return 1;
+            default:
+                throw new UnsupportedOperationException(
+                    String.format("Invalid hardwareBuffer format specified %d",
+                            hardwareBufferFormat));
+        }
+    }
+    /**
      * <p>
      * Copy source image data to destination Image.
      * </p>
diff --git a/media/java/android/media/MediaDescription.java b/media/java/android/media/MediaDescription.java
index dece6bd..458562a 100644
--- a/media/java/android/media/MediaDescription.java
+++ b/media/java/android/media/MediaDescription.java
@@ -142,10 +142,10 @@
         mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
-        mIcon = in.readParcelable(null, android.graphics.Bitmap.class);
-        mIconUri = in.readParcelable(null, android.net.Uri.class);
+        mIcon = in.readParcelable(null);
+        mIconUri = in.readParcelable(null);
         mExtras = in.readBundle();
-        mMediaUri = in.readParcelable(null, android.net.Uri.class);
+        mMediaUri = in.readParcelable(null);
     }
 
     /**
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 2427fa6..9c9e83b 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -371,7 +371,7 @@
         mFeatures = in.createStringArrayList();
         mType = in.readInt();
         mIsSystem = in.readBoolean();
-        mIconUri = in.readParcelable(null, android.net.Uri.class);
+        mIconUri = in.readParcelable(null);
         mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mConnectionState = in.readInt();
         mClientPackageName = in.readString();
diff --git a/media/java/android/media/PublicFormatUtils.java b/media/java/android/media/PublicFormatUtils.java
new file mode 100644
index 0000000..6268804
--- /dev/null
+++ b/media/java/android/media/PublicFormatUtils.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media;
+
+/**
+ * Package private utility class for PublicFormat related methods.
+ */
+class PublicFormatUtils {
+    public static int getHalFormat(int imageFormat) {
+        return nativeGetHalFormat(imageFormat);
+    }
+    public static long getHalDataspace(int imageFormat) {
+        return nativeGetHalDataspace(imageFormat);
+    }
+    public static int getPublicFormat(int imageFormat, long dataspace) {
+        return nativeGetPublicFormat(imageFormat, dataspace);
+    }
+    private static native int nativeGetHalFormat(int imageFormat);
+    private static native long nativeGetHalDataspace(int imageFormat);
+    private static native int nativeGetPublicFormat(int imageFormat, long dataspace);
+}
diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java
index c0793ec..f3f8bbe 100644
--- a/media/java/android/media/Spatializer.java
+++ b/media/java/android/media/Spatializer.java
@@ -29,7 +29,6 @@
 import android.media.permission.SafeCloseable;
 import android.os.RemoteException;
 import android.util.Log;
-import android.util.Pair;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -376,16 +375,8 @@
     public void addOnSpatializerStateChangedListener(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull OnSpatializerStateChangedListener listener) {
-        synchronized (mStateListenerLock) {
-            final Pair<ArrayList<ListenerInfo<OnSpatializerStateChangedListener>>,
-                    SpatializerInfoDispatcherStub> res =
-                    CallbackUtil.addListener("addOnSpatializerStateChangedListener",
-                            executor, listener, mStateListeners, mInfoDispatcherStub,
-                            () -> new SpatializerInfoDispatcherStub(),
-                            stub -> stub.register(true));
-            mStateListeners = res.first;
-            mInfoDispatcherStub = res.second;
-        }
+        mStateListenerMgr.addListener(executor, listener, "addOnSpatializerStateChangedListener",
+                () -> new SpatializerInfoDispatcherStub());
     }
 
     /**
@@ -396,15 +387,7 @@
      */
     public void removeOnSpatializerStateChangedListener(
             @NonNull OnSpatializerStateChangedListener listener) {
-        synchronized (mStateListenerLock) {
-            final Pair<ArrayList<ListenerInfo<OnSpatializerStateChangedListener>>,
-                    SpatializerInfoDispatcherStub> res =
-                    CallbackUtil.removeListener("removeOnSpatializerStateChangedListener",
-                            listener, mStateListeners, mInfoDispatcherStub,
-                            stub -> stub.register(false));
-            mStateListeners = res.first;
-            mInfoDispatcherStub = res.second;
-        }
+        mStateListenerMgr.removeListener(listener, "removeOnSpatializerStateChangedListener");
     }
 
     /**
@@ -459,18 +442,16 @@
         }
     }
 
-    private final Object mStateListenerLock = new Object();
     /**
-     * List of listeners for state listener and their associated Executor.
-     * List is lazy-initialized on first registration
+     * manages the OnSpatializerStateChangedListener listeners and the
+     * SpatializerInfoDispatcherStub
      */
-    @GuardedBy("mStateListenerLock")
-    private @Nullable ArrayList<ListenerInfo<OnSpatializerStateChangedListener>> mStateListeners;
+    private final CallbackUtil.LazyListenerManager<OnSpatializerStateChangedListener>
+            mStateListenerMgr = new CallbackUtil.LazyListenerManager();
 
-    @GuardedBy("mStateListenerLock")
-    private @Nullable SpatializerInfoDispatcherStub mInfoDispatcherStub;
-
-    private final class SpatializerInfoDispatcherStub extends ISpatializerCallback.Stub {
+    private final class SpatializerInfoDispatcherStub extends ISpatializerCallback.Stub
+            implements CallbackUtil.DispatcherStub {
+        @Override
         public void register(boolean register) {
             try {
                 if (register) {
@@ -486,7 +467,7 @@
         @Override
         @SuppressLint("GuardedBy") // lock applied inside callListeners method
         public void dispatchSpatializerEnabledChanged(boolean enabled) {
-            CallbackUtil.callListeners(mStateListeners, mStateListenerLock,
+            mStateListenerMgr.callListeners(
                     (listener) -> listener.onSpatializerEnabledChanged(
                             Spatializer.this, enabled));
         }
@@ -494,7 +475,7 @@
         @Override
         @SuppressLint("GuardedBy") // lock applied inside callListeners method
         public void dispatchSpatializerAvailableChanged(boolean available) {
-            CallbackUtil.callListeners(mStateListeners, mStateListenerLock,
+            mStateListenerMgr.callListeners(
                     (listener) -> listener.onSpatializerAvailableChanged(
                             Spatializer.this, available));
         }
@@ -612,16 +593,9 @@
     public void addOnHeadTrackingModeChangedListener(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull OnHeadTrackingModeChangedListener listener) {
-        synchronized (mHeadTrackingListenerLock) {
-            final Pair<ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>,
-                    SpatializerHeadTrackingDispatcherStub> res = CallbackUtil.addListener(
-                        "addOnHeadTrackingModeChangedListener", executor, listener,
-                        mHeadTrackingListeners, mHeadTrackingDispatcherStub,
-                        () -> new SpatializerHeadTrackingDispatcherStub(),
-                        stub -> stub.register(true));
-            mHeadTrackingListeners = res.first;
-            mHeadTrackingDispatcherStub = res.second;
-        }
+        mHeadTrackingListenerMgr.addListener(executor, listener,
+                "addOnHeadTrackingModeChangedListener",
+                 () -> new SpatializerHeadTrackingDispatcherStub());
     }
 
     /**
@@ -634,15 +608,8 @@
     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
     public void removeOnHeadTrackingModeChangedListener(
             @NonNull OnHeadTrackingModeChangedListener listener) {
-        synchronized (mHeadTrackingListenerLock) {
-            final Pair<ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>,
-                    SpatializerHeadTrackingDispatcherStub> res = CallbackUtil.removeListener(
-                        "removeOnHeadTrackingModeChangedListener", listener,
-                        mHeadTrackingListeners, mHeadTrackingDispatcherStub,
-                        stub -> stub.register(false));
-            mHeadTrackingListeners = res.first;
-            mHeadTrackingDispatcherStub = res.second;
-        }
+        mHeadTrackingListenerMgr.removeListener(listener,
+                "removeOnHeadTrackingModeChangedListener");
     }
 
     /**
@@ -828,20 +795,17 @@
     //-----------------------------------------------------------------------------
     // head tracking callback management and stub
 
-    private final Object mHeadTrackingListenerLock = new Object();
     /**
-     * List of listeners for head tracking mode listener and their associated Executor.
-     * List is lazy-initialized on first registration
+     * manages the OnHeadTrackingModeChangedListener listeners and the
+     * SpatializerHeadTrackingDispatcherStub
      */
-    @GuardedBy("mHeadTrackingListenerLock")
-    private @Nullable ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>
-            mHeadTrackingListeners;
-
-    @GuardedBy("mHeadTrackingListenerLock")
-    private @Nullable SpatializerHeadTrackingDispatcherStub mHeadTrackingDispatcherStub;
+    private final CallbackUtil.LazyListenerManager<OnHeadTrackingModeChangedListener>
+            mHeadTrackingListenerMgr = new CallbackUtil.LazyListenerManager();
 
     private final class SpatializerHeadTrackingDispatcherStub
-            extends ISpatializerHeadTrackingModeCallback.Stub {
+            extends ISpatializerHeadTrackingModeCallback.Stub
+            implements CallbackUtil.DispatcherStub {
+        @Override
         public void register(boolean register) {
             try {
                 if (register) {
@@ -857,14 +821,14 @@
         @Override
         @SuppressLint("GuardedBy") // lock applied inside callListeners method
         public void dispatchSpatializerActualHeadTrackingModeChanged(int mode) {
-            CallbackUtil.callListeners(mHeadTrackingListeners, mHeadTrackingListenerLock,
+            mHeadTrackingListenerMgr.callListeners(
                     (listener) -> listener.onHeadTrackingModeChanged(Spatializer.this, mode));
         }
 
         @Override
         @SuppressLint("GuardedBy") // lock applied inside callListeners method
         public void dispatchSpatializerDesiredHeadTrackingModeChanged(int mode) {
-            CallbackUtil.callListeners(mHeadTrackingListeners, mHeadTrackingListenerLock,
+            mHeadTrackingListenerMgr.callListeners(
                     (listener) -> listener.onDesiredHeadTrackingModeChanged(
                             Spatializer.this, mode));
         }
diff --git a/media/java/android/media/midi/MidiDeviceStatus.java b/media/java/android/media/midi/MidiDeviceStatus.java
index aa06267..b118279 100644
--- a/media/java/android/media/midi/MidiDeviceStatus.java
+++ b/media/java/android/media/midi/MidiDeviceStatus.java
@@ -115,7 +115,7 @@
         new Parcelable.Creator<MidiDeviceStatus>() {
         public MidiDeviceStatus createFromParcel(Parcel in) {
             ClassLoader classLoader = MidiDeviceInfo.class.getClassLoader();
-            MidiDeviceInfo deviceInfo = in.readParcelable(classLoader, android.media.midi.MidiDeviceInfo.class);
+            MidiDeviceInfo deviceInfo = in.readParcelable(classLoader);
             boolean[] inputPortOpen = in.createBooleanArray();
             int[] outputPortOpenCount = in.createIntArray();
             return new MidiDeviceStatus(deviceInfo, inputPortOpen, outputPortOpenCount);
diff --git a/media/java/android/media/musicrecognition/RecognitionRequest.java b/media/java/android/media/musicrecognition/RecognitionRequest.java
index b8757a3..3298d63 100644
--- a/media/java/android/media/musicrecognition/RecognitionRequest.java
+++ b/media/java/android/media/musicrecognition/RecognitionRequest.java
@@ -152,8 +152,8 @@
     }
 
     private RecognitionRequest(Parcel in) {
-        mAudioFormat = in.readParcelable(AudioFormat.class.getClassLoader(), android.media.AudioFormat.class);
-        mAudioAttributes = in.readParcelable(AudioAttributes.class.getClassLoader(), android.media.AudioAttributes.class);
+        mAudioFormat = in.readParcelable(AudioFormat.class.getClassLoader());
+        mAudioAttributes = in.readParcelable(AudioAttributes.class.getClassLoader());
         mCaptureSession = in.readInt();
         mMaxAudioLengthSeconds = in.readInt();
         mIgnoreBeginningFrames = in.readInt();
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 955ae3c..1da41fb 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -1022,7 +1022,7 @@
             mVolumeControl = in.readInt();
             mMaxVolume = in.readInt();
             mCurrentVolume = in.readInt();
-            mAudioAttrs = in.readParcelable(null, android.media.AudioAttributes.class);
+            mAudioAttrs = in.readParcelable(null);
             mVolumeControlId = in.readString();
         }
 
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index d34e636..770b8aa 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -66,6 +66,7 @@
             int seq, int userId);
     void releaseSession(in IBinder sessionToken, int userId);
     int getClientPid(in String sessionId);
+    int getClientPriority(int useCase, in String sessionId);
 
     void setMainSession(in IBinder sessionToken, int userId);
     void setSurface(in IBinder sessionToken, in Surface surface, int userId);
diff --git a/media/java/android/media/tv/TvContentRatingSystemInfo.java b/media/java/android/media/tv/TvContentRatingSystemInfo.java
index 947b2d6..f44ded3 100644
--- a/media/java/android/media/tv/TvContentRatingSystemInfo.java
+++ b/media/java/android/media/tv/TvContentRatingSystemInfo.java
@@ -94,8 +94,8 @@
     };
 
     private TvContentRatingSystemInfo(Parcel in) {
-        mXmlUri = in.readParcelable(null, android.net.Uri.class);
-        mApplicationInfo = in.readParcelable(null, android.content.pm.ApplicationInfo.class);
+        mXmlUri = in.readParcelable(null);
+        mApplicationInfo = in.readParcelable(null);
     }
 
     @Override
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index e60d537..54cb2bf 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -653,16 +653,16 @@
         mType = in.readInt();
         mIsHardwareInput = in.readByte() == 1;
         mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
-        mIconUri = in.readParcelable(null, android.net.Uri.class);
+        mIconUri = in.readParcelable(null);
         mLabelResId = in.readInt();
-        mIcon = in.readParcelable(null, android.graphics.drawable.Icon.class);
-        mIconStandby = in.readParcelable(null, android.graphics.drawable.Icon.class);
-        mIconDisconnected = in.readParcelable(null, android.graphics.drawable.Icon.class);
+        mIcon = in.readParcelable(null);
+        mIconStandby = in.readParcelable(null);
+        mIconDisconnected = in.readParcelable(null);
         mSetupActivity = in.readString();
         mCanRecord = in.readByte() == 1;
         mCanPauseRecording = in.readByte() == 1;
         mTunerCount = in.readInt();
-        mHdmiDeviceInfo = in.readParcelable(null, android.hardware.hdmi.HdmiDeviceInfo.class);
+        mHdmiDeviceInfo = in.readParcelable(null);
         mIsConnectedToHdmiSwitch = in.readByte() == 1;
         mHdmiConnectionRelativePosition = in.readInt();
         mParentId = in.readString();
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index ad86002..6420b4a 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -18,6 +18,7 @@
 
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -27,6 +28,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Rect;
+import android.media.AudioDeviceInfo;
+import android.media.AudioFormat.Encoding;
 import android.media.PlaybackParams;
 import android.media.tv.interactive.TvIAppManager;
 import android.net.Uri;
@@ -60,6 +63,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -764,6 +768,9 @@
                 @Override
                 public void run() {
                     mSessionCallback.onVideoAvailable(mSession);
+                    if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
+                        mSession.getIAppSession().notifyVideoAvailable();
+                    }
                 }
             });
         }
@@ -773,6 +780,9 @@
                 @Override
                 public void run() {
                     mSessionCallback.onVideoUnavailable(mSession, reason);
+                    if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
+                        mSession.getIAppSession().notifyVideoUnavailable(reason);
+                    }
                 }
             });
         }
@@ -782,6 +792,9 @@
                 @Override
                 public void run() {
                     mSessionCallback.onContentAllowed(mSession);
+                    if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
+                        mSession.getIAppSession().notifyContentAllowed();
+                    }
                 }
             });
         }
@@ -791,6 +804,9 @@
                 @Override
                 public void run() {
                     mSessionCallback.onContentBlocked(mSession, rating);
+                    if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
+                        mSession.getIAppSession().notifyContentBlocked(rating);
+                    }
                 }
             });
         }
@@ -1786,6 +1802,29 @@
     };
 
     /**
+     * Returns a priority for the given use case type and the client's foreground or background
+     * status.
+     *
+     * @param useCase the use case type of the client. When the given use case type is invalid,
+     *        the default use case type will be used. {@see TvInputService#PriorityHintUseCaseType}.
+     * @param sessionId the unique id of the session owned by the client. When {@code null},
+     *        the caller will be used as a client. When the session is invalid, background status
+     *        will be used as a client's status. Otherwise, TV app corresponding to the given
+     *        session id will be used as a client.
+     *        {@see TvInputService#onCreateSession(String, String)}.
+     *
+     * @return the use case priority value for the given use case type and the client's foreground
+     *         or background status.
+     *
+     * @hide
+     */
+    @SystemApi
+    public int getClientPriority(@TvInputService.PriorityHintUseCaseType int useCase,
+            @Nullable String sessionId) {
+        return getClientPriorityInternal(useCase, sessionId);
+    };
+
+    /**
      * Creates a recording {@link Session} for a given TV input.
      *
      * <p>The number of sessions that can be created at the same time is limited by the capability
@@ -1829,6 +1868,14 @@
         return clientPid;
     }
 
+    private int getClientPriorityInternal(int useCase, String sessionId) {
+        try {
+            return mService.getClientPriority(useCase, sessionId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * Returns the TvStreamConfig list of the given TV input.
      *
@@ -3178,6 +3225,16 @@
             return false;
         }
 
+        /**
+         * Override default audio sink from audio policy.
+         *
+         * @param audioType device type of the audio sink to override with.
+         * @param audioAddress device address of the audio sink to override with.
+         * @param samplingRate desired sampling rate. Use default when it's 0.
+         * @param channelMask desired channel mask. Use default when it's
+         *        AudioFormat.CHANNEL_OUT_DEFAULT.
+         * @param format desired format. Use default when it's AudioFormat.ENCODING_DEFAULT.
+         */
         public void overrideAudioSink(int audioType, String audioAddress, int samplingRate,
                 int channelMask, int format) {
             try {
@@ -3187,5 +3244,27 @@
                 throw new RuntimeException(e);
             }
         }
+
+        /**
+         * Override default audio sink from audio policy.
+         *
+         * @param device {@link android.media.AudioDeviceInfo} to use.
+         * @param samplingRate desired sampling rate. Use default when it's 0.
+         * @param channelMask desired channel mask. Use default when it's
+         *        AudioFormat.CHANNEL_OUT_DEFAULT.
+         * @param format desired format. Use default when it's AudioFormat.ENCODING_DEFAULT.
+         */
+        public void overrideAudioSink(@NonNull AudioDeviceInfo device,
+                @IntRange(from = 0) int samplingRate,
+                int channelMask, @Encoding int format) {
+            Objects.requireNonNull(device);
+            try {
+                mInterface.overrideAudioSink(
+                        AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType()),
+                        device.getAddress(), samplingRate, channelMask, format);
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+        }
     }
 }
diff --git a/media/java/android/media/tv/interactive/ITvIAppManager.aidl b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
index 23201fa..2e04359 100644
--- a/media/java/android/media/tv/interactive/ITvIAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
@@ -51,6 +51,10 @@
     void notifyTuned(in IBinder sessionToken, in Uri channelUri, int userId);
     void notifyTrackSelected(in IBinder sessionToken, int type, in String trackId, int userId);
     void notifyTracksChanged(in IBinder sessionToken, in List<TvTrackInfo> tracks, int userId);
+    void notifyVideoAvailable(in IBinder sessionToken, int userId);
+    void notifyVideoUnavailable(in IBinder sessionToken, int reason, int userId);
+    void notifyContentAllowed(in IBinder sessionToken, int userId);
+    void notifyContentBlocked(in IBinder sessionToken, in String rating, int userId);
     void setSurface(in IBinder sessionToken, in Surface surface, int userId);
     void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height,
             int userId);
diff --git a/media/java/android/media/tv/interactive/ITvIAppSession.aidl b/media/java/android/media/tv/interactive/ITvIAppSession.aidl
index 52f9a87..2788ff6 100644
--- a/media/java/android/media/tv/interactive/ITvIAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppSession.aidl
@@ -42,6 +42,10 @@
     void notifyTuned(in Uri channelUri);
     void notifyTrackSelected(int type, in String trackId);
     void notifyTracksChanged(in List<TvTrackInfo> tracks);
+    void notifyVideoAvailable();
+    void notifyVideoUnavailable(int reason);
+    void notifyContentAllowed();
+    void notifyContentBlocked(in String rating);
     void setSurface(in Surface surface);
     void dispatchSurfaceChanged(int format, int width, int height);
     void notifyBroadcastInfoResponse(in BroadcastInfoResponse response);
diff --git a/media/java/android/media/tv/interactive/TvIAppManager.java b/media/java/android/media/tv/interactive/TvIAppManager.java
index d1fd1df..9685e3a 100644
--- a/media/java/android/media/tv/interactive/TvIAppManager.java
+++ b/media/java/android/media/tv/interactive/TvIAppManager.java
@@ -26,6 +26,7 @@
 import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.BroadcastInfoResponse;
+import android.media.tv.TvContentRating;
 import android.media.tv.TvInputManager;
 import android.media.tv.TvTrackInfo;
 import android.net.Uri;
@@ -1037,6 +1038,66 @@
             }
         }
 
+        /**
+         * Notifies IAPP session when video is available.
+         */
+        public void notifyVideoAvailable() {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyVideoAvailable(mToken, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Notifies IAPP session when video is unavailable.
+         */
+        public void notifyVideoUnavailable(int reason) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyVideoUnavailable(mToken, reason, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Notifies IAPP session when content is allowed.
+         */
+        public void notifyContentAllowed() {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyContentAllowed(mToken, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Notifies IAPP session when content is blocked.
+         */
+        public void notifyContentBlocked(TvContentRating rating) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyContentBlocked(mToken, rating.flattenToString(), mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         private void flushPendingEventsLocked() {
             mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
 
diff --git a/media/java/android/media/tv/interactive/TvIAppService.java b/media/java/android/media/tv/interactive/TvIAppService.java
index 0456041..4993bc3 100644
--- a/media/java/android/media/tv/interactive/TvIAppService.java
+++ b/media/java/android/media/tv/interactive/TvIAppService.java
@@ -31,6 +31,7 @@
 import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.BroadcastInfoResponse;
+import android.media.tv.TvContentRating;
 import android.media.tv.TvTrackInfo;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -442,6 +443,34 @@
         }
 
         /**
+         * Called when video is available.
+         * @hide
+         */
+        public void onVideoAvailable() {
+        }
+
+        /**
+         * Called when video is unavailable.
+         * @hide
+         */
+        public void onVideoUnavailable(int reason) {
+        }
+
+        /**
+         * Called when content is allowed.
+         * @hide
+         */
+        public void onContentAllowed() {
+        }
+
+        /**
+         * Called when content is blocked.
+         * @hide
+         */
+        public void onContentBlocked(TvContentRating rating) {
+        }
+
+        /**
          * Called when a broadcast info response is received.
          * @hide
          */
@@ -816,6 +845,33 @@
             onTracksChanged(tracks);
         }
 
+        void notifyVideoAvailable() {
+            if (DEBUG) {
+                Log.d(TAG, "notifyVideoAvailable");
+            }
+            onVideoAvailable();
+        }
+
+        void notifyVideoUnavailable(int reason) {
+            if (DEBUG) {
+                Log.d(TAG, "notifyVideoAvailable (reason=" + reason + ")");
+            }
+            onVideoUnavailable(reason);
+        }
+
+        void notifyContentAllowed() {
+            if (DEBUG) {
+                Log.d(TAG, "notifyContentAllowed");
+            }
+            notifyContentAllowed();
+        }
+
+        void notifyContentBlocked(TvContentRating rating) {
+            if (DEBUG) {
+                Log.d(TAG, "notifyContentBlocked (rating=" + rating.flattenToString() + ")");
+            }
+            onContentBlocked(rating);
+        }
 
         /**
          * Calls {@link #onBroadcastInfoResponse}.
@@ -1182,6 +1238,26 @@
         }
 
         @Override
+        public void notifyVideoAvailable() {
+            mSessionImpl.notifyVideoAvailable();
+        }
+
+        @Override
+        public void notifyVideoUnavailable(int reason) {
+            mSessionImpl.notifyVideoUnavailable(reason);
+        }
+
+        @Override
+        public void notifyContentAllowed() {
+            mSessionImpl.notifyContentAllowed();
+        }
+
+        @Override
+        public void notifyContentBlocked(String rating) {
+            mSessionImpl.notifyContentBlocked(TvContentRating.unflattenFromString(rating));
+        }
+
+        @Override
         public void setSurface(Surface surface) {
             mSessionImpl.setSurface(surface);
         }
diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java
index 6a6a22c..50a2083 100644
--- a/media/java/android/media/tv/tuner/Lnb.java
+++ b/media/java/android/media/tv/tuner/Lnb.java
@@ -28,6 +28,9 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -145,9 +148,9 @@
 
     private static final String TAG = "Lnb";
 
-    LnbCallback mCallback;
-    Executor mExecutor;
-    Tuner mTuner;
+    Map<LnbCallback, Executor> mCallbackMap =
+            new HashMap<LnbCallback, Executor>();
+    Tuner mOwner;
     private final Object mCallbackLock = new Object();
 
 
@@ -164,38 +167,82 @@
 
     private Lnb() {}
 
-    void setCallback(Executor executor, @Nullable LnbCallback callback, Tuner tuner) {
+    void setCallbackAndOwner(Executor executor, @Nullable LnbCallback callback, Tuner tuner) {
         synchronized (mCallbackLock) {
-            mCallback = callback;
-            mExecutor = executor;
-            mTuner = tuner;
+            if (callback != null && executor != null) {
+                addCallback(callback, executor);
+            }
+        }
+        setOwner(tuner);
+    }
+
+    /**
+     * Adds LnbCallback
+     *
+     * @param callback the callback to receive notifications from LNB.
+     * @param executor the executor on which callback will be invoked. Cannot be null.
+     */
+    public void addCallback(@NonNull  LnbCallback callback, @NonNull Executor executor) {
+        Objects.requireNonNull(callback, "callback must not be null");
+        Objects.requireNonNull(executor, "executor must not be null");
+        synchronized (mCallbackLock) {
+            mCallbackMap.put(callback, executor);
+        }
+    }
+
+    /**
+     * Removes LnbCallback
+     *
+     * @param callback the callback be removed for callback
+     *
+     * @return {@code true} when successful. {@code false} otherwise.
+     */
+    public boolean removeCallback(@NonNull LnbCallback callback) {
+        Objects.requireNonNull(callback, "callback must not be null");
+        synchronized (mCallbackLock) {
+            boolean result = (mCallbackMap.remove(callback) != null);
+            return result;
+        }
+    }
+
+    // allow owner transfer independent of whether callback is registered or not
+    /* package */ void setOwner(@NonNull Tuner newOwner) {
+        Objects.requireNonNull(newOwner, "newOwner must not be null");
+        synchronized (mLock) {
+            mOwner = newOwner;
         }
     }
 
     private void onEvent(int eventType) {
         synchronized (mCallbackLock) {
-            if (mExecutor != null && mCallback != null) {
-                mExecutor.execute(() -> {
-                    synchronized (mCallbackLock) {
-                        if (mCallback != null) {
-                            mCallback.onEvent(eventType);
+            for (LnbCallback callback : mCallbackMap.keySet()) {
+                Executor executor = mCallbackMap.get(callback);
+                if (callback != null && executor != null) {
+                    executor.execute(() -> {
+                        synchronized (mCallbackLock) {
+                            if (callback != null) {
+                                callback.onEvent(eventType);
+                            }
                         }
-                    }
-                });
+                    });
+                }
             }
         }
     }
 
     private void onDiseqcMessage(byte[] diseqcMessage) {
         synchronized (mCallbackLock) {
-            if (mExecutor != null && mCallback != null) {
-                mExecutor.execute(() -> {
-                    synchronized (mCallbackLock) {
-                        if (mCallback != null) {
-                            mCallback.onDiseqcMessage(diseqcMessage);
+            for (LnbCallback callback : mCallbackMap.keySet()) {
+                Executor executor = mCallbackMap.get(callback);
+                if (callback != null && executor != null) {
+                    executor.execute(() -> {
+                        synchronized (mCallbackLock) {
+                            if (callback != null) {
+                                callback.onDiseqcMessage(diseqcMessage);
+                            }
                         }
-                    }
-                });
+                    });
+                }
             }
         }
     }
@@ -279,7 +326,11 @@
                 TunerUtils.throwExceptionForResult(res, "Failed to close LNB");
             } else {
                 mIsClosed = true;
-                mTuner.releaseLnb();
+                if (mOwner != null) {
+                    mOwner.releaseLnb();
+                    mOwner = null;
+                }
+                mCallbackMap.clear();
             }
         }
     }
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 4128abf..9c4a83a 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -241,7 +241,7 @@
 
 
     private static final String TAG = "MediaTvTuner";
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final int MSG_RESOURCE_LOST = 1;
     private static final int MSG_ON_FILTER_EVENT = 2;
@@ -250,7 +250,6 @@
 
     private static final int FILTER_CLEANUP_THRESHOLD = 256;
 
-
     /** @hide */
     @IntDef(prefix = "DVR_TYPE_", value = {DVR_TYPE_RECORD, DVR_TYPE_PLAYBACK})
     @Retention(RetentionPolicy.SOURCE)
@@ -455,6 +454,260 @@
     }
 
     /**
+     * Transfers the ownership of shared frontend and its associated resources.
+     *
+     * @param newOwner the Tuner instance to be the new owner.
+     *
+     * @return result status of tune operation.
+     */
+    public int transferOwner(@NonNull Tuner newOwner) {
+        acquireTRMSLock("transferOwner()");
+        mFrontendLock.lock();
+        mFrontendCiCamLock.lock();
+        mLnbLock.lock();
+        try {
+
+            if (!isFrontendOwner() || !isNewOwnerQualifiedForTransfer(newOwner)) {
+                return RESULT_INVALID_STATE;
+            }
+
+            int res = transferFeOwner(newOwner);
+            if (res != RESULT_SUCCESS) {
+                return res;
+            }
+
+            res = transferCiCamOwner(newOwner);
+            if (res != RESULT_SUCCESS) {
+                return res;
+            }
+
+            res = transferLnbOwner(newOwner);
+            if (res != RESULT_SUCCESS) {
+                return res;
+            }
+        } finally {
+            mFrontendLock.unlock();
+            mFrontendCiCamLock.unlock();
+            mLnbLock.unlock();
+            releaseTRMSLock();
+        }
+        return RESULT_SUCCESS;
+    }
+
+    /**
+     * Resets or copies Frontend related settings.
+     */
+    private void replicateFrontendSettings(@Nullable Tuner src) {
+        mFrontendLock.lock();
+        try {
+            if (src == null) {
+                if (DEBUG) {
+                    Log.d(TAG, "resetting Frontend params for " + mClientId);
+                }
+                mFrontend = null;
+                mFrontendHandle = null;
+                mFrontendInfo = null;
+                mFrontendType = FrontendSettings.TYPE_UNDEFINED;
+            } else {
+                if (DEBUG) {
+                    Log.d(TAG, "copying Frontend params from " + src.mClientId
+                            + " to " + mClientId);
+                }
+                mFrontend = src.mFrontend;
+                mFrontendHandle = src.mFrontendHandle;
+                mFrontendInfo = src.mFrontendInfo;
+                mFrontendType = src.mFrontendType;
+            }
+        } finally {
+            mFrontendLock.unlock();
+        }
+    }
+
+    /**
+     * Sets the frontend owner. mFeOwnerTuner should be null for the owner Tuner instance.
+     */
+    private void setFrontendOwner(Tuner owner) {
+        mFrontendLock.lock();
+        try {
+            mFeOwnerTuner = owner;
+        } finally {
+            mFrontendLock.unlock();
+        }
+    }
+
+    /**
+     * Resets or copies the CiCam related settings.
+     */
+    private void replicateCiCamSettings(@Nullable Tuner src) {
+        mFrontendCiCamLock.lock();
+        try {
+            if (src == null) {
+                if (DEBUG) {
+                    Log.d(TAG, "resetting CiCamParams: " + mClientId);
+                }
+                mFrontendCiCamHandle = null;
+                mFrontendCiCamId = null;
+            } else {
+                if (DEBUG) {
+                    Log.d(TAG, "copying CiCamParams from " + src.mClientId + " to " + mClientId);
+                    Log.d(TAG, "mFrontendCiCamHandle:" + src.mFrontendCiCamHandle + ", "
+                            + "mFrontendCiCamId:" + src.mFrontendCiCamId);
+                }
+                mFrontendCiCamHandle = src.mFrontendCiCamHandle;
+                mFrontendCiCamId = src.mFrontendCiCamId;
+            }
+        } finally {
+            mFrontendCiCamLock.unlock();
+        }
+    }
+
+    /**
+     * Resets or copies Lnb related settings.
+     */
+    private void replicateLnbSettings(@Nullable Tuner src) {
+        mLnbLock.lock();
+        try {
+            if (src == null) {
+                if (DEBUG) {
+                    Log.d(TAG, "resetting Lnb params");
+                }
+                mLnb = null;
+                mLnbHandle = null;
+            } else {
+                if (DEBUG) {
+                    Log.d(TAG, "copying Lnb params from " + src.mClientId + " to " + mClientId);
+                }
+                mLnb = src.mLnb;
+                mLnbHandle = src.mLnbHandle;
+            }
+        } finally {
+            mLnbLock.unlock();
+        }
+    }
+
+    /**
+     * Checks if it is a frontend resource owner.
+     * Proper mutex must be held prior to calling this.
+     */
+    private boolean isFrontendOwner() {
+        boolean notAnOwner = (mFeOwnerTuner != null);
+        if (notAnOwner) {
+            Log.e(TAG, "transferOwner() - cannot be called on the non-owner");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Checks if the newOwner is qualified.
+     * Proper mutex must be held prior to calling this.
+     */
+    private boolean isNewOwnerQualifiedForTransfer(@NonNull Tuner newOwner) {
+        // new owner must be the current sharee
+        boolean newOwnerIsTheCurrentSharee = (newOwner.mFeOwnerTuner == this)
+                && (newOwner.mFrontendHandle.equals(mFrontendHandle));
+        if (!newOwnerIsTheCurrentSharee) {
+            Log.e(TAG, "transferOwner() - new owner must be the current sharee");
+            return false;
+        }
+
+        // new owner must not be holding any of the to-be-shared resources
+        boolean newOwnerAlreadyHoldsToBeSharedResource =
+                (newOwner.mFrontendCiCamHandle != null || newOwner.mLnb != null);
+        if (newOwnerAlreadyHoldsToBeSharedResource) {
+            Log.e(TAG, "transferOwner() - new owner cannot be holding CiCam"
+                    + " nor Lnb resource");
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Transfers the ownership of the already held frontend resource.
+     * Proper mutex must be held prior to calling this.
+     */
+    private int transferFeOwner(@NonNull Tuner newOwner) {
+        // handle native resource first
+        newOwner.nativeUpdateFrontend(getNativeContext());
+        nativeUpdateFrontend(0);
+
+        // transfer frontend related settings
+        newOwner.replicateFrontendSettings(this);
+
+        // transfer the frontend owner info
+        setFrontendOwner(newOwner);
+        newOwner.setFrontendOwner(null);
+
+        // handle TRM
+        if (mTunerResourceManager.transferOwner(
+                TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND,
+                mClientId, newOwner.mClientId)) {
+            return RESULT_SUCCESS;
+        } else {
+            return RESULT_UNKNOWN_ERROR;
+        }
+    }
+
+    /**
+     * Transfers the ownership of CiCam resource.
+     * This is a no-op if the CiCam resource is not held.
+     * Proper mutex must be held prior to calling this.
+     */
+    private int transferCiCamOwner(Tuner newOwner) {
+        boolean notAnOwner = (mFrontendCiCamHandle == null);
+        if (notAnOwner) {
+            // There is nothing to do here if there is no CiCam
+            return RESULT_SUCCESS;
+        }
+
+        // no need to handle at native level
+
+        // transfer the CiCam info at Tuner level
+        newOwner.replicateCiCamSettings(this);
+        replicateCiCamSettings(null);
+
+        // handle TRM
+        if (mTunerResourceManager.transferOwner(
+                TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM,
+                mClientId, newOwner.mClientId)) {
+            return RESULT_SUCCESS;
+        } else {
+            return RESULT_UNKNOWN_ERROR;
+        }
+    }
+
+    /**
+     * Transfers the ownership of Lnb resource.
+     * This is a no-op if the Lnb resource is not held.
+     * Proper mutex must be held prior to calling this.
+     */
+    private int transferLnbOwner(Tuner newOwner) {
+        boolean notAnOwner = (mLnb == null);
+        if (notAnOwner) {
+            // There is nothing to do here if there is no Lnb
+            return RESULT_SUCCESS;
+        }
+
+        // no need to handle at native level
+
+        // set the new owner
+        mLnb.setOwner(newOwner);
+
+        newOwner.replicateLnbSettings(this);
+        replicateLnbSettings(null);
+
+        // handle TRM
+        if (mTunerResourceManager.transferOwner(
+                TunerResourceManager.TUNER_RESOURCE_TYPE_LNB,
+                mClientId, newOwner.mClientId)) {
+            return RESULT_SUCCESS;
+        } else {
+            return RESULT_UNKNOWN_ERROR;
+        }
+    }
+
+    /**
      * Updates client priority with an arbitrary value along with a nice value.
      *
      * <p>Tuner resource manager (TRM) uses the client priority value to decide whether it is able
@@ -547,59 +800,114 @@
         }
     }
 
+    /**
+     * Either unshares the frontend resource (for sharee) or release Frontend (for owner)
+     */
+    public void closeFrontend() {
+        acquireTRMSLock("closeFrontend()");
+        try {
+            releaseFrontend();
+        } finally {
+            releaseTRMSLock();
+        }
+    }
+
+    /**
+     * Releases frontend resource for the owner. Unshares frontend resource for the sharee.
+     */
     private void releaseFrontend() {
+        if (DEBUG) {
+            Log.d(TAG, "Tuner#releaseFrontend");
+        }
         mFrontendLock.lock();
         try {
             if (mFrontendHandle != null) {
+                if (DEBUG) {
+                    Log.d(TAG, "mFrontendHandle not null");
+                }
                 if (mFeOwnerTuner != null) {
+                    if (DEBUG) {
+                        Log.d(TAG, "mFeOwnerTuner not null - sharee");
+                    }
                     // unregister self from the Frontend callback
                     mFeOwnerTuner.unregisterFrontendCallbackListener(this);
                     mFeOwnerTuner = null;
+                    nativeUnshareFrontend();
                 } else {
+                    if (DEBUG) {
+                        Log.d(TAG, "mFeOwnerTuner null - owner");
+                    }
                     // close resource as owner
                     int res = nativeCloseFrontend(mFrontendHandle);
                     if (res != Tuner.RESULT_SUCCESS) {
                         TunerUtils.throwExceptionForResult(res, "failed to close frontend");
                     }
-                    mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId);
                 }
+                if (DEBUG) {
+                    Log.d(TAG, "call TRM#releaseFrontend :" + mFrontendHandle + ", " + mClientId);
+                }
+                mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId);
                 FrameworkStatsLog
                         .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
                         FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN);
-                mFrontendHandle = null;
-                mFrontend = null;
+                replicateFrontendSettings(null);
             }
         } finally {
             mFrontendLock.unlock();
         }
     }
 
+    /**
+     * Releases CiCam resource if held. No-op otherwise.
+     */
+    private void releaseCiCam() {
+        mFrontendCiCamLock.lock();
+        try {
+            if (mFrontendCiCamHandle != null) {
+                if (DEBUG) {
+                    Log.d(TAG, "unlinking CiCam : " + mFrontendCiCamHandle + " for " +  mClientId);
+                }
+                int result = nativeUnlinkCiCam(mFrontendCiCamId);
+                if (result == RESULT_SUCCESS) {
+                    mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId);
+                    replicateCiCamSettings(null);
+                } else {
+                    Log.e(TAG, "nativeUnlinkCiCam(" + mFrontendCiCamHandle + ") for mClientId:"
+                            + mClientId + "failed with result:" + result);
+                }
+            } else {
+                if (DEBUG) {
+                    Log.d(TAG, "NOT unlinking CiCam : " + mClientId);
+                }
+            }
+        } finally {
+            mFrontendCiCamLock.unlock();
+        }
+    }
+
     private void releaseAll() {
+        // release CiCam before frontend because frontend handle is needed to unlink CiCam
+        releaseCiCam();
+
         releaseFrontend();
 
         mLnbLock.lock();
         try {
             // mLnb will be non-null only for owner tuner
             if (mLnb != null) {
+                if (DEBUG) {
+                    Log.d(TAG, "calling mLnb.close() : " + mClientId);
+                }
                 mLnb.close();
+            } else {
+                if (DEBUG) {
+                    Log.d(TAG, "NOT calling mLnb.close() : " + mClientId);
+                }
             }
         } finally {
             mLnbLock.unlock();
         }
 
-        mFrontendCiCamLock.lock();
-        try {
-            if (mFrontendCiCamHandle != null) {
-                int result = nativeUnlinkCiCam(mFrontendCiCamId);
-                if (result == RESULT_SUCCESS) {
-                    mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId);
-                    mFrontendCiCamId = null;
-                    mFrontendCiCamHandle = null;
-                }
-            }
-        } finally {
-            mFrontendCiCamLock.unlock();
-        }
 
         synchronized (mDescramblers) {
             if (!mDescramblers.isEmpty()) {
@@ -669,8 +977,11 @@
      */
     private native Frontend nativeOpenFrontendByHandle(int handle);
     private native int nativeShareFrontend(int id);
+    private native int nativeUnshareFrontend();
     private native void nativeRegisterFeCbListener(long nativeContext);
     private native void nativeUnregisterFeCbListener(long nativeContext);
+    // nativeUpdateFrontend must be called on the new owner first
+    private native void nativeUpdateFrontend(long nativeContext);
     @Result
     private native int nativeTune(int type, FrontendSettings settings);
     private native int nativeStopTune();
@@ -997,6 +1308,21 @@
             mFrontendHandle = feHandle[0];
             mFrontend = nativeOpenFrontendByHandle(mFrontendHandle);
         }
+
+        // For satellite type, set Lnb if valid handle exists.
+        // This is necessary as now that we support closeFrontend().
+        if (mFrontendType == FrontendSettings.TYPE_DVBS
+                || mFrontendType == FrontendSettings.TYPE_ISDBS
+                || mFrontendType == FrontendSettings.TYPE_ISDBS3) {
+            mLnbLock.lock();
+            try {
+                if (mLnbHandle != null && mLnb != null) {
+                    nativeSetLnb(mLnb);
+                }
+            } finally {
+                mLnbLock.unlock();
+            }
+        }
         return granted;
     }
 
@@ -1756,12 +2082,12 @@
             Objects.requireNonNull(executor, "executor must not be null");
             Objects.requireNonNull(cb, "LnbCallback must not be null");
             if (mLnb != null) {
-                mLnb.setCallback(executor, cb, this);
+                mLnb.setCallbackAndOwner(executor, cb, this);
                 return mLnb;
             }
             if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, mLnbLock)
                     && mLnb != null) {
-                mLnb.setCallback(executor, cb, this);
+                mLnb.setCallbackAndOwner(executor, cb, this);
                 setLnb(mLnb);
                 return mLnb;
             }
@@ -1795,7 +2121,7 @@
                     mLnbHandle = null;
                 }
                 mLnb = newLnb;
-                mLnb.setCallback(executor, cb, this);
+                mLnb.setCallbackAndOwner(executor, cb, this);
                 setLnb(mLnb);
             }
             return mLnb;
@@ -2081,8 +2407,15 @@
         try {
             if (mLnbHandle != null) {
                 // LNB handle can be null if it's opened by name.
+                if (DEBUG) {
+                    Log.d(TAG, "releasing Lnb");
+                }
                 mTunerResourceManager.releaseLnb(mLnbHandle, mClientId);
                 mLnbHandle = null;
+            } else {
+                if (DEBUG) {
+                    Log.d(TAG, "NOT releasing Lnb because mLnbHandle is null");
+                }
             }
             mLnb = null;
         } finally {
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
index 45c2a5a..9f44236 100644
--- a/media/java/android/media/tv/tuner/filter/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -626,8 +626,12 @@
      * be a no-op. Use {@link TunerVersionChecker#getTunerVersion()} to get the version information.
      *
      * @param delayInMs specifies the duration of the delay in milliseconds.
+     * @return one of the following results: {@link Tuner#RESULT_SUCCESS},
+     * {@link Tuner#RESULT_UNAVAILABLE}, {@link Tuner#RESULT_NOT_INITIALIZED},
+     * {@link Tuner#RESULT_INVALID_STATE}, {@link Tuner#RESULT_INVALID_ARGUMENT},
+     * {@link Tuner#RESULT_OUT_OF_MEMORY}, or {@link Tuner#RESULT_UNKNOWN_ERROR}.
      */
-    public int delayCallbackUntilTimeMillis(long delayInMs) {
+    public int delayCallbackUntilMillisElapsed(long delayInMs) {
         if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
                   TunerVersionChecker.TUNER_VERSION_2_0, "setTimeDelayHint")) {
             return Tuner.RESULT_UNAVAILABLE;
@@ -652,8 +656,12 @@
      * be a no-op. Use {@link TunerVersionChecker#getTunerVersion()} to get the version information.
      *
      * @param delayInBytes specifies the duration of the delay in bytes.
+     * @return one of the following results: {@link Tuner#RESULT_SUCCESS},
+     * {@link Tuner#RESULT_UNAVAILABLE}, {@link Tuner#RESULT_NOT_INITIALIZED},
+     * {@link Tuner#RESULT_INVALID_STATE}, {@link Tuner#RESULT_INVALID_ARGUMENT},
+     * {@link Tuner#RESULT_OUT_OF_MEMORY}, or {@link Tuner#RESULT_UNKNOWN_ERROR}.
      */
-    public int delayCallbackUntilBufferFilled(int delayInBytes) {
+    public int delayCallbackUntilBytesAccumulated(int delayInBytes) {
         if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
                   TunerVersionChecker.TUNER_VERSION_2_0, "setTimeDelayHint")) {
             return Tuner.RESULT_UNAVAILABLE;
diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
index fe611c7..5ada89e 100644
--- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
+++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
@@ -21,9 +21,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
+import android.annotation.SuppressLint;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.media.tv.TvInputService;
 import android.os.Binder;
 import android.os.RemoteException;
 import android.util.Log;
@@ -415,6 +418,25 @@
     }
 
     /**
+     * Transfers the ownership of shared resource.
+     *
+     * <p><strong>Note:</strong> Only the existing frontend sharee can be the new owner.
+     *
+     * @param resourceType the type of the resource to transfer the ownership for.
+     * @param currentOwnerId the id of the current owner client.
+     * @param newOwnerId the id of the new owner client.
+     *
+     * @return true if successful and false otherwise.
+     */
+    public boolean transferOwner(int resourceType, int currentOwnerId, int newOwnerId) {
+        try {
+            return mService.transferOwner(resourceType, currentOwnerId, newOwnerId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Requests a Tuner Demux resource.
      *
      * <p>There are three possible scenarios:
@@ -702,6 +724,49 @@
     }
 
     /**
+     * Returns a priority for the given use case type and the client's foreground or background
+     * status.
+     *
+     * @param useCase the use case type of the client. When the given use case type is invalid,
+     *        the default use case type will be used. {@see TvInputService#PriorityHintUseCaseType}.
+     * @param pid the pid of the client. When the pid is invalid, background status will be used as
+     *        a client's status. Otherwise, client's app corresponding to the given session id will
+     *        be used as a client. {@see TvInputService#onCreateSession(String, String)}.
+     *
+     * @return the client priority..
+     */
+    public int getClientPriority(@TvInputService.PriorityHintUseCaseType int useCase, int pid) {
+        try {
+            return mService.getClientPriority(useCase, pid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns a config priority for the given use case type and the foreground or background
+     * status.
+     *
+     * @param useCase the use case type of the client. When the given use case type is invalid,
+     *        the default use case type will be used. {@see TvInputService#PriorityHintUseCaseType}.
+     * @param isForeground {@code true} if foreground, {@code false} otherwise.
+     *
+     * @return the config priority.
+     *
+     * @hide
+     */
+    @TestApi
+    @SuppressLint("ShowingMemberInHiddenClass")
+    public int getConfigPriority(@TvInputService.PriorityHintUseCaseType int useCase,
+            boolean isForeground) {
+        try {
+            return mService.getConfigPriority(useCase, isForeground);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Interface used to receive events from TunerResourceManager.
      */
     public abstract static class ResourcesReclaimListener {
diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
index 5f35820..d16fc6c 100644
--- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
+++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
@@ -177,6 +177,19 @@
     void shareFrontend(in int selfClientId, in int targetClientId);
 
     /*
+     * Transfers the ownership of the shared resource.
+     *
+     * <p><strong>Note:</strong> Only the existing frontend sharee can be the new owner.
+     *
+     * @param resourceType the type of resource to transfer the ownership for.
+     * @param currentOwnerId the id of the current owner client.
+     * @param newOwnerId the id of the new owner client.
+     *
+     * @return true if successful. false otherwise.
+     */
+    boolean transferOwner(in int resourceType, in int currentOwnerId, in int newOwnerId);
+
+    /*
      * This API is used by the Tuner framework to request an available demux from the TunerHAL.
      *
      * <p>There are three possible scenarios:
@@ -442,4 +455,30 @@
      * guaranteed to work and may be unrecoverrable. (This should not happen.)
      */
     boolean releaseLock(in int clientId);
+
+    /**
+     * Returns a priority for the given use case type and the client's foreground or background
+     * status.
+     *
+     * @param useCase the use case type of the client. When the given use case type is invalid,
+     *        the default use case type will be used. {@see TvInputService#PriorityHintUseCaseType}.
+     * @param pid the pid of the client. When the pid is invalid, background status will be used as
+     *        a client's status. Otherwise, client's app corresponding to the given session id will
+     *        be used as a client. {@see TvInputService#onCreateSession(String, String)}.
+     *
+     * @return the client priority..
+     */
+    int getClientPriority(int useCase, int pid);
+
+    /**
+     * Returns a config priority for the given use case type and the foreground or background
+     * status.
+     *
+     * @param useCase the use case type of the client. When the given use case type is invalid,
+     *        the default use case type will be used. {@see TvInputService#PriorityHintUseCaseType}.
+     * @param isForeground {@code true} if foreground, {@code false} otherwise.
+     *
+     * @return the config priority.
+     */
+    int getConfigPriority(int useCase, boolean isForeground);
 }
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index e817f2d..feae606 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -39,6 +39,7 @@
         "android_media_MediaProfiles.cpp",
         "android_media_MediaRecorder.cpp",
         "android_media_MediaSync.cpp",
+        "android_media_PublicFormatUtils.cpp",
         "android_media_ResampleInputStream.cpp",
         "android_media_Streams.cpp",
         "android_media_SyncParams.cpp",
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 021507c..6002e28 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -375,18 +375,13 @@
 }
 
 static void ImageReader_init(JNIEnv* env, jobject thiz, jobject weakThiz, jint width, jint height,
-                             jint format, jint maxImages, jlong ndkUsage)
-{
+                             jint maxImages, jlong ndkUsage, jint nativeFormat, jlong dataSpace) {
     status_t res;
-    int nativeFormat;
-    android_dataspace nativeDataspace;
 
-    ALOGV("%s: width:%d, height: %d, format: 0x%x, maxImages:%d",
-          __FUNCTION__, width, height, format, maxImages);
+    ALOGV("%s: width:%d, height: %d, nativeFormat: %d, maxImages:%d",
+          __FUNCTION__, width, height, nativeFormat, maxImages);
 
-    PublicFormat publicFormat = static_cast<PublicFormat>(format);
-    nativeFormat = mapPublicFormatToHalFormat(publicFormat);
-    nativeDataspace = mapPublicFormatToHalDataspace(publicFormat);
+    android_dataspace nativeDataspace = static_cast<android_dataspace>(dataSpace);
 
     jclass clazz = env->GetObjectClass(thiz);
     if (clazz == NULL) {
@@ -400,7 +395,7 @@
     BufferQueue::createBufferQueue(&gbProducer, &gbConsumer);
     sp<BufferItemConsumer> bufferConsumer;
     String8 consumerName = String8::format("ImageReader-%dx%df%xm%d-%d-%d",
-            width, height, format, maxImages, getpid(),
+            width, height, nativeFormat, maxImages, getpid(),
             createProcessUniqueId());
     uint64_t consumerUsage =
             android_hardware_HardwareBuffer_convertToGrallocUsageBits(ndkUsage);
@@ -527,7 +522,8 @@
     ALOGV("%s: Image (format: 0x%x) has been released", __FUNCTION__, ctx->getBufferFormat());
 }
 
-static jint ImageReader_imageSetup(JNIEnv* env, jobject thiz, jobject image) {
+static jint ImageReader_imageSetup(JNIEnv* env, jobject thiz, jobject image,
+                                   jboolean legacyValidateImageFormat) {
     ALOGV("%s:", __FUNCTION__);
     JNIImageReaderContext* ctx = ImageReader_getContext(env, thiz);
     if (ctx == NULL) {
@@ -590,7 +586,7 @@
             ALOGV("%s: Producer buffer size: %dx%d, doesn't match ImageReader configured size: %dx%d",
                     __FUNCTION__, outputWidth, outputHeight, imageReaderWidth, imageReaderHeight);
         }
-        if (imgReaderFmt != bufferFormat) {
+        if (legacyValidateImageFormat && imgReaderFmt != bufferFormat) {
             if (imgReaderFmt == HAL_PIXEL_FORMAT_YCbCr_420_888 &&
                     isPossiblyYUV(bufferFormat)) {
                 // Treat formats that are compatible with flexible YUV
@@ -958,10 +954,10 @@
 
 static const JNINativeMethod gImageReaderMethods[] = {
     {"nativeClassInit",        "()V",                        (void*)ImageReader_classInit },
-    {"nativeInit",             "(Ljava/lang/Object;IIIIJ)V",  (void*)ImageReader_init },
+    {"nativeInit",             "(Ljava/lang/Object;IIIJIJ)V",   (void*)ImageReader_init },
     {"nativeClose",            "()V",                        (void*)ImageReader_close },
     {"nativeReleaseImage",     "(Landroid/media/Image;)V",   (void*)ImageReader_imageRelease },
-    {"nativeImageSetup",       "(Landroid/media/Image;)I",   (void*)ImageReader_imageSetup },
+    {"nativeImageSetup",       "(Landroid/media/Image;Z)I",   (void*)ImageReader_imageSetup },
     {"nativeGetSurface",       "()Landroid/view/Surface;",   (void*)ImageReader_getSurface },
     {"nativeDetachImage",      "(Landroid/media/Image;)I",   (void*)ImageReader_detachImage },
     {"nativeCreateImagePlanes",
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 8dcdc98..a548a47 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -1454,6 +1454,7 @@
 extern int register_android_media_MediaMuxer(JNIEnv *env);
 extern int register_android_media_MediaRecorder(JNIEnv *env);
 extern int register_android_media_MediaSync(JNIEnv *env);
+extern int register_android_media_PublicFormatUtils(JNIEnv *env);
 extern int register_android_media_ResampleInputStream(JNIEnv *env);
 extern int register_android_media_MediaProfiles(JNIEnv *env);
 extern int register_android_mtp_MtpDatabase(JNIEnv *env);
@@ -1501,6 +1502,11 @@
         goto bail;
     }
 
+    if (register_android_media_PublicFormatUtils(env) < 0) {
+        ALOGE("ERROR: PublicFormatUtils native registration failed\n");
+        goto bail;
+    }
+
     if (register_android_media_ResampleInputStream(env) < 0) {
         ALOGE("ERROR: ResampleInputStream native registration failed\n");
         goto bail;
diff --git a/media/jni/android_media_PublicFormatUtils.cpp b/media/jni/android_media_PublicFormatUtils.cpp
new file mode 100644
index 0000000..09ebdee
--- /dev/null
+++ b/media/jni/android_media_PublicFormatUtils.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "PublicFormatUtils_JNI"
+
+#include <utils/misc.h>
+#include <ui/PublicFormat.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <jni.h>
+
+using namespace android;
+
+static jint android_media_PublicFormatUtils_getHalFormat(JNIEnv* /*env*/, jobject /*thiz*/,
+                                                         jint imageFormat) {
+    PublicFormat publicFormat = static_cast<PublicFormat>(imageFormat);
+    int nativeFormat = mapPublicFormatToHalFormat(publicFormat);
+    return static_cast<jint>(nativeFormat);
+}
+
+static jlong android_media_PublicFormatUtils_getHalDataspace(JNIEnv* /*env*/, jobject /*thiz*/,
+                                                             jint imageFormat) {
+    PublicFormat publicFormat = static_cast<PublicFormat>(imageFormat);
+    android_dataspace
+        nativeDataspace = mapPublicFormatToHalDataspace(publicFormat);
+    return static_cast<jlong>(nativeDataspace);
+}
+
+static jint android_media_PublicFormatUtils_getPublicFormat(JNIEnv* /*env*/, jobject /*thiz*/,
+                                                            jint hardwareBufferFormat,
+                                                            jlong dataspace) {
+    PublicFormat nativeFormat = mapHalFormatDataspaceToPublicFormat(
+            hardwareBufferFormat, static_cast<android_dataspace>(dataspace));
+    return static_cast<jint>(nativeFormat);
+}
+
+static const JNINativeMethod gMethods[] = {
+    {"nativeGetHalFormat",    "(I)I", (void*)android_media_PublicFormatUtils_getHalFormat},
+    {"nativeGetHalDataspace", "(I)J", (void*)android_media_PublicFormatUtils_getHalDataspace},
+    {"nativeGetPublicFormat", "(IJ)I",(void*)android_media_PublicFormatUtils_getPublicFormat}
+};
+
+int register_android_media_PublicFormatUtils(JNIEnv *env) {
+    return AndroidRuntime::registerNativeMethods(env,
+             "android/media/PublicFormatUtils", gMethods, NELEM(gMethods));
+}
+
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 9572e180..1b41494 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -461,6 +461,7 @@
 }
 
 MediaEvent::~MediaEvent() {
+    android::Mutex::Autolock autoLock(mLock);
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     env->DeleteWeakGlobalRef(mMediaEventObj);
     mMediaEventObj = nullptr;
@@ -977,7 +978,8 @@
 void FrontendClientCallbackImpl::addCallbackListener(JTuner* jtuner, jweak listener) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     jweak listenerRef = env->NewWeakGlobalRef(listener);
-    ALOGV("addCallbackListener() with listener:%p and ref:%p @%p", listener, listenerRef, this);
+    ALOGV("addCallbackListener() with listener:%p and ref:%p @%p",
+              listener, listenerRef, this);
     std::scoped_lock<std::mutex> lock(mMutex);
     mListenersMap[jtuner] = listenerRef;
 }
@@ -1344,18 +1346,43 @@
     return (int)Result::SUCCESS;
 }
 
+int JTuner::unshareFrontend() {
+    if (mFeClient != nullptr) {
+        ALOGE("Cannot unshare frontend because this session is already holding %d"
+              " as an owner instead of as a sharee", mFeClient->getId());
+        return (int)Result::INVALID_STATE;
+    }
+
+    mSharedFeId = (int)Constant::INVALID_FRONTEND_ID;
+    return (int)Result::SUCCESS;
+}
+
 void JTuner::registerFeCbListener(JTuner* jtuner) {
+    ALOGV("registerFeCbListener: %p", jtuner);
     if (mFeClientCb != nullptr && jtuner != nullptr) {
         mFeClientCb->addCallbackListener(jtuner, jtuner->getObject());
     }
 }
 
 void JTuner::unregisterFeCbListener(JTuner* jtuner) {
+    ALOGV("unregisterFeCbListener: %p", jtuner);
     if (mFeClientCb != nullptr && jtuner != nullptr) {
         mFeClientCb->removeCallbackListener(jtuner);
     }
 }
 
+void JTuner::updateFrontend(JTuner* jtuner) {
+    if (jtuner == nullptr) {
+        ALOGV("JTuner::updateFrontend(null) called for previous owner: %p", this);
+        mFeClient = nullptr;
+        mFeClientCb = nullptr;
+    } else {
+        ALOGV("JTuner::updateFrontend(%p) called for new owner: %p", jtuner, this);
+        mFeClient = jtuner->mFeClient;
+        mFeClientCb = jtuner->mFeClientCb;
+    }
+}
+
 jobject JTuner::getAnalogFrontendCaps(JNIEnv *env, FrontendCapabilities &caps) {
     jclass clazz = env->FindClass("android/media/tv/tuner/frontend/AnalogFrontendCapabilities");
     jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V");
@@ -3318,6 +3345,12 @@
     return tuner->shareFrontend(id);
 }
 
+static int android_media_tv_Tuner_unshare_frontend(
+        JNIEnv *env, jobject thiz) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    return tuner->unshareFrontend();
+}
+
 static void android_media_tv_Tuner_register_fe_cb_listener(
         JNIEnv *env, jobject thiz, jlong shareeJTuner) {
     sp<JTuner> tuner = getTuner(env, thiz);
@@ -3332,6 +3365,17 @@
     tuner->unregisterFeCbListener(jtuner);
 }
 
+static void android_media_tv_Tuner_update_frontend(JNIEnv *env, jobject thiz, jlong jtunerPtr) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    JTuner *jtuner;
+    if (jtunerPtr == 0) {
+        jtuner = nullptr;
+    } else {
+        jtuner = (JTuner *) jtunerPtr;
+    }
+    tuner->updateFrontend(jtuner);
+}
+
 static int android_media_tv_Tuner_tune(JNIEnv *env, jobject thiz, jint type, jobject settings) {
     sp<JTuner> tuner = getTuner(env, thiz);
     FrontendSettings setting = getFrontendSettings(env, type, settings);
@@ -4573,10 +4617,14 @@
             (void *)android_media_tv_Tuner_open_frontend_by_handle },
     { "nativeShareFrontend", "(I)I",
             (void *)android_media_tv_Tuner_share_frontend },
+    { "nativeUnshareFrontend", "()I",
+            (void *)android_media_tv_Tuner_unshare_frontend },
     { "nativeRegisterFeCbListener", "(J)V",
             (void*)android_media_tv_Tuner_register_fe_cb_listener },
     { "nativeUnregisterFeCbListener", "(J)V",
             (void*)android_media_tv_Tuner_unregister_fe_cb_listener },
+    { "nativeUpdateFrontend", "(J)V",
+            (void*)android_media_tv_Tuner_update_frontend },
     { "nativeTune", "(ILandroid/media/tv/tuner/frontend/FrontendSettings;)I",
             (void *)android_media_tv_Tuner_tune },
     { "nativeStopTune", "()I", (void *)android_media_tv_Tuner_stop_tune },
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index f1b31e3..502bd6b 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -178,8 +178,10 @@
     jobject getFrontendIds();
     jobject openFrontendByHandle(int feHandle);
     int shareFrontend(int feId);
+    int unshareFrontend();
     void registerFeCbListener(JTuner* jtuner);
     void unregisterFeCbListener(JTuner* jtuner);
+    void updateFrontend(JTuner* jtuner);
     jint closeFrontendById(int id);
     jobject getFrontendInfo(int id);
     int tune(const FrontendSettings& settings);
diff --git a/media/jni/soundpool/Stream.cpp b/media/jni/soundpool/Stream.cpp
index 773cdc9..50bb79c 100644
--- a/media/jni/soundpool/Stream.cpp
+++ b/media/jni/soundpool/Stream.cpp
@@ -275,118 +275,104 @@
         float leftVolume, float rightVolume, int32_t priority, int32_t loop, float rate,
         std::vector<std::any>& garbage)
 {
-    // oldTrack and newTrack are placeholders to be released by garbage without the lock.
-    sp<AudioTrack> oldTrack;
-    sp<AudioTrack> newTrack;
-    status_t status = NO_ERROR;
+    ALOGV("%s(%p)(soundID=%d, streamID=%d, leftVolume=%f, rightVolume=%f,"
+            " priority=%d, loop=%d, rate=%f)",
+            __func__, this, sound->getSoundID(), nextStreamID, leftVolume, rightVolume,
+            priority, loop, rate);
 
-    {
-        ALOGV("%s(%p)(soundID=%d, streamID=%d, leftVolume=%f, rightVolume=%f,"
-                " priority=%d, loop=%d, rate=%f)",
-                __func__, this, sound->getSoundID(), nextStreamID, leftVolume, rightVolume,
-                priority, loop, rate);
+    // initialize track
+    const audio_stream_type_t streamType =
+            AudioSystem::attributesToStreamType(*mStreamManager->getAttributes());
+    const int32_t channelCount = sound->getChannelCount();
+    const auto sampleRate = (uint32_t)lround(double(sound->getSampleRate()) * rate);
+    size_t frameCount = 0;
 
-        // initialize track
-        const audio_stream_type_t streamType =
-                AudioSystem::attributesToStreamType(*mStreamManager->getAttributes());
-        const int32_t channelCount = sound->getChannelCount();
-        const auto sampleRate = (uint32_t)lround(double(sound->getSampleRate()) * rate);
-        size_t frameCount = 0;
+    if (loop) {
+        const audio_format_t format = sound->getFormat();
+        const size_t frameSize = audio_is_linear_pcm(format)
+                ? channelCount * audio_bytes_per_sample(format) : 1;
+        frameCount = sound->getSizeInBytes() / frameSize;
+    }
 
-        if (loop) {
-            const audio_format_t format = sound->getFormat();
-            const size_t frameSize = audio_is_linear_pcm(format)
-                    ? channelCount * audio_bytes_per_sample(format) : 1;
-            frameCount = sound->getSizeInBytes() / frameSize;
-        }
-
-        // check if the existing track has the same sound id.
-        if (mAudioTrack != nullptr && mSoundID == sound->getSoundID()) {
+    if (mAudioTrack != nullptr) {
+        if (mSoundID == sound->getSoundID()
+                && mAudioTrack->setSampleRate(sampleRate) == NO_ERROR) {
+            // Reuse the old track if the soundID matches.
             // the sample rate may fail to change if the audio track is a fast track.
-            if (mAudioTrack->setSampleRate(sampleRate) == NO_ERROR) {
-                newTrack = mAudioTrack;
-                ALOGV("%s: reusing track %p for sound %d",
-                        __func__, mAudioTrack.get(), sound->getSoundID());
-            }
-        }
-        if (newTrack == nullptr) {
-            // mToggle toggles each time a track is started on a given stream.
-            // The toggle is concatenated with the Stream address and passed to AudioTrack
-            // as callback user data. This enables the detection of callbacks received from the old
-            // audio track while the new one is being started and avoids processing them with
-            // wrong audio audio buffer size  (mAudioBufferSize)
-            auto toggle = mToggle ^ 1;
-            // NOLINTNEXTLINE(performance-no-int-to-ptr)
-            void* userData = reinterpret_cast<void*>((uintptr_t)this | toggle);
-            audio_channel_mask_t soundChannelMask = sound->getChannelMask();
-            // When sound contains a valid channel mask, use it as is.
-            // Otherwise, use stream count to calculate channel mask.
-            audio_channel_mask_t channelMask = soundChannelMask != AUDIO_CHANNEL_NONE
-                    ? soundChannelMask : audio_channel_out_mask_from_count(channelCount);
-
-            // do not create a new audio track if current track is compatible with sound parameters
-
-            android::content::AttributionSourceState attributionSource;
-            attributionSource.packageName = mStreamManager->getOpPackageName();
-            attributionSource.token = sp<BBinder>::make();
-            // TODO b/182469354 make consistent with AudioRecord, add util for native source
-            newTrack = new AudioTrack(streamType, sampleRate, sound->getFormat(),
-                    channelMask, sound->getIMemory(), AUDIO_OUTPUT_FLAG_FAST,
-                    staticCallback, userData,
-                    0 /*default notification frames*/, AUDIO_SESSION_ALLOCATE,
-                    AudioTrack::TRANSFER_DEFAULT,
-                    nullptr /*offloadInfo*/, attributionSource,
-                    mStreamManager->getAttributes(),
-                    false /*doNotReconnect*/, 1.0f /*maxRequiredSpeed*/);
-            // Set caller name so it can be logged in destructor.
-            // MediaMetricsConstants.h: AMEDIAMETRICS_PROP_CALLERNAME_VALUE_SOUNDPOOL
-            newTrack->setCallerName("soundpool");
-            oldTrack = mAudioTrack;
-            status = newTrack->initCheck();
-            if (status != NO_ERROR) {
-                ALOGE("%s: error creating AudioTrack", __func__);
-                // newTrack goes out of scope, so reference count drops to zero
-                goto exit;
-            }
-            // From now on, AudioTrack callbacks received with previous toggle value will be ignored.
-            mToggle = toggle;
-            mAudioTrack = newTrack;
-            ALOGV("%s: using new track %p for sound %d",
-                    __func__, newTrack.get(), sound->getSoundID());
-        }
-        if (mMuted) {
-            newTrack->setVolume(0.0f, 0.0f);
+            ALOGV("%s: reusing track %p for sound %d",
+                    __func__, mAudioTrack.get(), sound->getSoundID());
         } else {
-            newTrack->setVolume(leftVolume, rightVolume);
+            // If reuse not possible, move mAudioTrack to garbage, set to nullptr.
+            garbage.emplace_back(std::move(mAudioTrack));
+            mAudioTrack.clear(); // move should have cleared the sp<>, but we clear just in case.
         }
-        newTrack->setLoop(0, frameCount, loop);
-        mAudioTrack->start();
-        mSound = sound;
-        mSoundID = sound->getSoundID();
-        mPriority = priority;
-        mLoop = loop;
-        mLeftVolume = leftVolume;
-        mRightVolume = rightVolume;
-        mRate = rate;
-        mState = PLAYING;
-        mStopTimeNs = 0;
-        mStreamID = nextStreamID;  // prefer this to be the last, as it is an atomic sync point
     }
+    if (mAudioTrack == nullptr) {
+        // mToggle toggles each time a track is started on a given stream.
+        // The toggle is concatenated with the Stream address and passed to AudioTrack
+        // as callback user data. This enables the detection of callbacks received from the old
+        // audio track while the new one is being started and avoids processing them with
+        // wrong audio audio buffer size  (mAudioBufferSize)
+        auto toggle = mToggle ^ 1;
+        // NOLINTNEXTLINE(performance-no-int-to-ptr)
+        void* userData = reinterpret_cast<void*>((uintptr_t)this | toggle);
+        audio_channel_mask_t soundChannelMask = sound->getChannelMask();
+        // When sound contains a valid channel mask, use it as is.
+        // Otherwise, use stream count to calculate channel mask.
+        audio_channel_mask_t channelMask = soundChannelMask != AUDIO_CHANNEL_NONE
+                ? soundChannelMask : audio_channel_out_mask_from_count(channelCount);
 
-exit:
-    ALOGV("%s: delete oldTrack %p", __func__, oldTrack.get());
-    if (status != NO_ERROR) {
-        // TODO: should we consider keeping the soundID if the old track is OK?
-        // Do not attempt to restart this track (should we remove the stream id?)
-        mState = IDLE;
-        mSoundID = 0;
-        mSound.reset();
-        mAudioTrack.clear();  // actual release from garbage
+        // do not create a new audio track if current track is compatible with sound parameters
+
+        android::content::AttributionSourceState attributionSource;
+        attributionSource.packageName = mStreamManager->getOpPackageName();
+        attributionSource.token = sp<BBinder>::make();
+        // TODO b/182469354 make consistent with AudioRecord, add util for native source
+        mAudioTrack = new AudioTrack(streamType, sampleRate, sound->getFormat(),
+                channelMask, sound->getIMemory(), AUDIO_OUTPUT_FLAG_FAST,
+                staticCallback, userData,
+                0 /*default notification frames*/, AUDIO_SESSION_ALLOCATE,
+                AudioTrack::TRANSFER_DEFAULT,
+                nullptr /*offloadInfo*/, attributionSource,
+                mStreamManager->getAttributes(),
+                false /*doNotReconnect*/, 1.0f /*maxRequiredSpeed*/);
+        // Set caller name so it can be logged in destructor.
+        // MediaMetricsConstants.h: AMEDIAMETRICS_PROP_CALLERNAME_VALUE_SOUNDPOOL
+        mAudioTrack->setCallerName("soundpool");
+
+        if (status_t status = mAudioTrack->initCheck();
+            status != NO_ERROR) {
+            ALOGE("%s: error %d creating AudioTrack", __func__, status);
+            // TODO: should we consider keeping the soundID and reusing the old track?
+            mState = IDLE;
+            mSoundID = 0;
+            mSound.reset();
+            garbage.emplace_back(std::move(mAudioTrack)); // remove mAudioTrack.
+            mAudioTrack.clear(); // move should have cleared the sp<>, but we clear just in case.
+            return;
+        }
+        // From now on, AudioTrack callbacks received with previous toggle value will be ignored.
+        mToggle = toggle;
+        ALOGV("%s: using new track %p for sound %d",
+                __func__, mAudioTrack.get(), sound->getSoundID());
     }
-
-    // move tracks to garbage to be released later outside of lock.
-    if (newTrack) garbage.emplace_back(std::move(newTrack));
-    if (oldTrack) garbage.emplace_back(std::move(oldTrack));
+    if (mMuted) {
+        mAudioTrack->setVolume(0.f, 0.f);
+    } else {
+        mAudioTrack->setVolume(leftVolume, rightVolume);
+    }
+    mAudioTrack->setLoop(0, frameCount, loop);
+    mAudioTrack->start();
+    mSound = sound;
+    mSoundID = sound->getSoundID();
+    mPriority = priority;
+    mLoop = loop;
+    mLeftVolume = leftVolume;
+    mRightVolume = rightVolume;
+    mRate = rate;
+    mState = PLAYING;
+    mStopTimeNs = 0;
+    mStreamID = nextStreamID;  // prefer this to be the last, as it is an atomic sync point
 }
 
 /* static */
diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp
index 8568383..959e756 100644
--- a/media/jni/tuner/FilterClient.cpp
+++ b/media/jni/tuner/FilterClient.cpp
@@ -43,6 +43,7 @@
 }
 
 FilterClient::~FilterClient() {
+    Mutex::Autolock _l(mLock);
     mTunerFilter = nullptr;
     mAvSharedHandle = nullptr;
     mAvSharedMemSize = 0;
@@ -74,6 +75,7 @@
     Result res;
     checkIsPassthroughFilter(configure);
 
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->configure(configure);
         res = ClientHelper::getServiceSpecificErrorCode(s);
@@ -87,6 +89,7 @@
 }
 
 Result FilterClient::configureMonitorEvent(int32_t monitorEventType) {
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->configureMonitorEvent(monitorEventType);
         return ClientHelper::getServiceSpecificErrorCode(s);
@@ -96,6 +99,7 @@
 }
 
 Result FilterClient::configureIpFilterContextId(int32_t cid) {
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->configureIpFilterContextId(cid);
         return ClientHelper::getServiceSpecificErrorCode(s);
@@ -105,6 +109,7 @@
 }
 
 Result FilterClient::configureAvStreamType(AvStreamType avStreamType) {
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->configureAvStreamType(avStreamType);
         return ClientHelper::getServiceSpecificErrorCode(s);
@@ -114,6 +119,7 @@
 }
 
 Result FilterClient::start() {
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->start();
         return ClientHelper::getServiceSpecificErrorCode(s);
@@ -123,6 +129,7 @@
 }
 
 Result FilterClient::stop() {
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->stop();
         return ClientHelper::getServiceSpecificErrorCode(s);
@@ -132,6 +139,7 @@
 }
 
 Result FilterClient::flush() {
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->flush();
         return ClientHelper::getServiceSpecificErrorCode(s);
@@ -141,6 +149,7 @@
 }
 
 Result FilterClient::getId(int32_t& id) {
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->getId(&id);
         return ClientHelper::getServiceSpecificErrorCode(s);
@@ -150,6 +159,7 @@
 }
 
 Result FilterClient::getId64Bit(int64_t& id) {
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->getId64Bit(&id);
         return ClientHelper::getServiceSpecificErrorCode(s);
@@ -159,6 +169,7 @@
 }
 
 Result FilterClient::releaseAvHandle(native_handle_t* handle, uint64_t avDataId) {
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->releaseAvHandle(dupToAidl(handle), avDataId);
         return ClientHelper::getServiceSpecificErrorCode(s);
@@ -168,6 +179,7 @@
 }
 
 Result FilterClient::setDataSource(sp<FilterClient> filterClient){
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->setDataSource(filterClient->getAidlFilter());
         return ClientHelper::getServiceSpecificErrorCode(s);
@@ -177,6 +189,7 @@
 }
 
 Result FilterClient::close() {
+    Mutex::Autolock _l(mLock);
     if (mFilterMQEventFlag != nullptr) {
         EventFlag::deleteEventFlag(&mFilterMQEventFlag);
         mFilterMQEventFlag = nullptr;
@@ -197,6 +210,7 @@
 }
 
 string FilterClient::acquireSharedFilterToken() {
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         string filterToken;
         if (mTunerFilter->acquireSharedFilterToken(&filterToken).isOk()) {
@@ -208,6 +222,7 @@
 }
 
 Result FilterClient::freeSharedFilterToken(const string& filterToken) {
+    Mutex::Autolock _l(mLock);
     if (mTunerFilter != nullptr) {
         Status s = mTunerFilter->freeSharedFilterToken(filterToken);
         return ClientHelper::getServiceSpecificErrorCode(s);
@@ -237,6 +252,7 @@
 }
 
 Result FilterClient::getFilterMq() {
+    Mutex::Autolock _l(mLock);
     if (mFilterMQ != nullptr) {
         return Result::SUCCESS;
     }
diff --git a/media/jni/tuner/FilterClient.h b/media/jni/tuner/FilterClient.h
index 20e5610..9e9b233 100644
--- a/media/jni/tuner/FilterClient.h
+++ b/media/jni/tuner/FilterClient.h
@@ -21,6 +21,7 @@
 #include <aidl/android/media/tv/tuner/BnTunerFilterCallback.h>
 #include <aidl/android/media/tv/tuner/ITunerFilter.h>
 #include <fmq/AidlMessageQueue.h>
+#include <utils/Mutex.h>
 
 #include "ClientHelper.h"
 #include "FilterClientCallback.h"
@@ -37,6 +38,7 @@
 using ::aidl::android::media::tv::tuner::BnTunerFilterCallback;
 using ::aidl::android::media::tv::tuner::ITunerFilter;
 using ::android::hardware::EventFlag;
+using ::android::Mutex;
 
 using namespace std;
 
@@ -179,6 +181,7 @@
     uint64_t mAvSharedMemSize;
     bool mIsMediaFilter;
     bool mIsPassthroughFilter;
+    Mutex mLock;
 };
 }  // namespace android
 
diff --git a/native/android/choreographer.cpp b/native/android/choreographer.cpp
index deee5b1..fbd4b2e 100644
--- a/native/android/choreographer.cpp
+++ b/native/android/choreographer.cpp
@@ -71,11 +71,12 @@
         const AChoreographerFrameCallbackData* data, size_t index) {
     return AChoreographerFrameCallbackData_routeGetFrameTimelineVsyncId(data, index);
 }
-int64_t AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTime(
+int64_t AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTimeNanos(
         const AChoreographerFrameCallbackData* data, size_t index) {
-    return AChoreographerFrameCallbackData_routeGetFrameTimelineExpectedPresentTime(data, index);
+    return AChoreographerFrameCallbackData_routeGetFrameTimelineExpectedPresentTimeNanos(data,
+                                                                                         index);
 }
-int64_t AChoreographerFrameCallbackData_getFrameTimelineDeadline(
+int64_t AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos(
         const AChoreographerFrameCallbackData* data, size_t index) {
-    return AChoreographerFrameCallbackData_routeGetFrameTimelineDeadline(data, index);
+    return AChoreographerFrameCallbackData_routeGetFrameTimelineDeadlineNanos(data, index);
 }
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 3c1aa44..35c794e 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -34,8 +34,8 @@
     AChoreographerFrameCallbackData_getFrameTimelinesLength;  # introduced=33
     AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex;  # introduced=33
     AChoreographerFrameCallbackData_getFrameTimelineVsyncId;  # introduced=33
-    AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTime;  # introduced=33
-    AChoreographerFrameCallbackData_getFrameTimelineDeadline;  # introduced=33
+    AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTimeNanos;  # introduced=33
+    AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos;  # introduced=33
     AConfiguration_copy;
     AConfiguration_delete;
     AConfiguration_diff;
diff --git a/native/android/libandroid_net.map.txt b/native/android/libandroid_net.map.txt
index a6c1b50..32fd734 100644
--- a/native/android/libandroid_net.map.txt
+++ b/native/android/libandroid_net.map.txt
@@ -18,6 +18,10 @@
     android_getprocnetwork; # llndk
     android_setprocdns; # llndk
     android_getprocdns; # llndk
+    # These functions have been part of the NDK since API 33.
+    android_tag_socket_with_uid; # llndk
+    android_tag_socket;  # llndk
+    android_untag_socket; # llndk
   local:
     *;
 };
diff --git a/native/android/net.c b/native/android/net.c
index e2f36a7..d7c22e1 100644
--- a/native/android/net.c
+++ b/native/android/net.c
@@ -161,3 +161,15 @@
 void android_res_cancel(int nsend_fd) {
     resNetworkCancel(nsend_fd);
 }
+
+int android_tag_socket_with_uid(int sockfd, int tag, uid_t uid) {
+    return tagSocket(sockfd, tag, uid);
+}
+
+int android_tag_socket(int sockfd, int tag) {
+    return tagSocket(sockfd, tag, -1);
+}
+
+int android_untag_socket(int sockfd) {
+    return untagSocket(sockfd);
+}
diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java
index f684a4d..d33666d 100644
--- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java
+++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java
@@ -545,6 +545,15 @@
     }
 
     /**
+     * Collects tagged summary results and sets summary enumeration mode.
+     * @throws RemoteException
+     */
+    void startTaggedSummaryEnumeration() throws RemoteException {
+        mSummary = mSession.getTaggedSummaryForAllUid(mTemplate, mStartTimeStamp, mEndTimeStamp);
+        mEnumerationIndex = 0;
+    }
+
+    /**
      * Collects history results for uid and resets history enumeration index.
      */
     void startHistoryEnumeration(int uid, int tag, int state) {
diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
index a316b8a..cc7b2a5 100644
--- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -54,6 +54,7 @@
 
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Provides access to network usage history and statistics. Usage data is collected in
@@ -368,7 +369,7 @@
      * @return Statistics which is described above.
      * @hide
      */
-    @Nullable
+    @NonNull
     // @SystemApi(client = MODULE_LIBRARIES)
     @WorkerThread
     public NetworkStats querySummary(@NonNull NetworkTemplate template, long startTime,
@@ -385,6 +386,39 @@
     }
 
     /**
+     * Query tagged network usage statistics summaries.
+     *
+     * The results will only include tagged traffic made by UIDs belonging to the calling user
+     * profile. The results are aggregated over time, so that all buckets will have the same
+     * start and end timestamps as the passed arguments. Not aggregated over state, uid,
+     * default network, metered, or roaming.
+     * This may take a long time, and apps should avoid calling this on their main thread.
+     *
+     * @param template Template used to match networks. See {@link NetworkTemplate}.
+     * @param startTime Start of period, in milliseconds since the Unix epoch, see
+     *            {@link System#currentTimeMillis}.
+     * @param endTime End of period, in milliseconds since the Unix epoch, see
+     *            {@link System#currentTimeMillis}.
+     * @return Statistics which is described above.
+     * @hide
+     */
+    @NonNull
+    // @SystemApi(client = MODULE_LIBRARIES)
+    @WorkerThread
+    public NetworkStats queryTaggedSummary(@NonNull NetworkTemplate template, long startTime,
+            long endTime) throws SecurityException {
+        try {
+            NetworkStats result =
+                    new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
+            result.startTaggedSummaryEnumeration();
+            return result;
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return null; // To make the compiler happy.
+    }
+
+    /**
      * Query network usage statistics details for a given uid.
      * This may take a long time, and apps should avoid calling this on their main thread.
      *
@@ -535,6 +569,31 @@
         return result;
     }
 
+    /**
+     * Query realtime network usage statistics details with interfaces constrains.
+     * Return snapshot of current UID statistics, including any {@link TrafficStats#UID_TETHERING},
+     * video calling data usage and count of network operations that set by
+     * {@link TrafficStats#incrementOperationCount}. The returned data doesn't include any
+     * statistics that is reported by {@link NetworkStatsProvider}.
+     *
+     * @param requiredIfaces A list of interfaces the stats should be restricted to, or
+     *               {@link NetworkStats#INTERFACES_ALL}.
+     *
+     * @hide
+     */
+    //@SystemApi
+    @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
+    @NonNull public android.net.NetworkStats getDetailedUidStats(
+                @NonNull Set<String> requiredIfaces) {
+        Objects.requireNonNull(requiredIfaces, "requiredIfaces cannot be null");
+        try {
+            return mService.getDetailedUidStats(requiredIfaces.toArray(new String[0]));
+        } catch (RemoteException e) {
+            if (DBG) Log.d(TAG, "Remote exception when get detailed uid stats");
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /** @hide */
     public void registerUsageCallback(NetworkTemplate template, int networkType,
             long thresholdBytes, UsageCallback callback, @Nullable Handler handler) {
diff --git a/packages/ConnectivityT/framework-t/src/android/net/DataUsageRequest.java b/packages/ConnectivityT/framework-t/src/android/net/DataUsageRequest.java
index f0ff465..b06d515 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/DataUsageRequest.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/DataUsageRequest.java
@@ -75,7 +75,7 @@
                 @Override
                 public DataUsageRequest createFromParcel(Parcel in) {
                     int requestId = in.readInt();
-                    NetworkTemplate template = in.readParcelable(null, android.net.NetworkTemplate.class);
+                    NetworkTemplate template = in.readParcelable(null);
                     long thresholdInBytes = in.readLong();
                     DataUsageRequest result = new DataUsageRequest(requestId, template,
                             thresholdInBytes);
diff --git a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl
index dfedf66..babe0bf 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl
+++ b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl
@@ -46,6 +46,10 @@
      */
     @UnsupportedAppUsage
     NetworkStats getSummaryForAllUid(in NetworkTemplate template, long start, long end, boolean includeTags);
+
+    /** Return network layer usage summary per UID for tagged traffic that matches template. */
+    NetworkStats getTaggedSummaryForAllUid(in NetworkTemplate template, long start, long end);
+
     /** Return historical network layer stats for specific UID traffic that matches template. */
     @UnsupportedAppUsage
     NetworkStatsHistory getHistoryForUid(in NetworkTemplate template, int uid, int set, int tag, int fields);
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java
index 03bb187..575c5ed 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java
@@ -267,14 +267,14 @@
         mMode = in.readInt();
         mSourceAddress = in.readString();
         mDestinationAddress = in.readString();
-        mNetwork = (Network) in.readParcelable(Network.class.getClassLoader(), android.net.Network.class);
+        mNetwork = (Network) in.readParcelable(Network.class.getClassLoader());
         mSpiResourceId = in.readInt();
         mEncryption =
-                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader(), android.net.IpSecAlgorithm.class);
+                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
         mAuthentication =
-                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader(), android.net.IpSecAlgorithm.class);
+                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
         mAuthenticatedEncryption =
-                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader(), android.net.IpSecAlgorithm.class);
+                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
         mEncapType = in.readInt();
         mEncapSocketResourceId = in.readInt();
         mEncapRemotePort = in.readInt();
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java
index 390af82..732cf19 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java
@@ -81,7 +81,7 @@
         status = in.readInt();
         resourceId = in.readInt();
         port = in.readInt();
-        fileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader(), android.os.ParcelFileDescriptor.class);
+        fileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
     }
 
     @android.annotation.NonNull
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java
index d577aa8..3915634 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java
@@ -73,9 +73,9 @@
 
     /** @hide */
     public NetworkStateSnapshot(@NonNull Parcel in) {
-        mNetwork = in.readParcelable(null, android.net.Network.class);
-        mNetworkCapabilities = in.readParcelable(null, android.net.NetworkCapabilities.class);
-        mLinkProperties = in.readParcelable(null, android.net.LinkProperties.class);
+        mNetwork = in.readParcelable(null);
+        mNetworkCapabilities = in.readParcelable(null);
+        mLinkProperties = in.readParcelable(null);
         mSubscriberId = in.readString();
         mLegacyType = in.readInt();
     }
diff --git a/packages/ConnectivityT/framework-t/src/android/net/UnderlyingNetworkInfo.java b/packages/ConnectivityT/framework-t/src/android/net/UnderlyingNetworkInfo.java
index 7ab53b1..33f9375 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/UnderlyingNetworkInfo.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/UnderlyingNetworkInfo.java
@@ -60,7 +60,7 @@
         mOwnerUid = in.readInt();
         mIface = in.readString();
         List<String> underlyingIfaces = new ArrayList<>();
-        in.readList(underlyingIfaces, null /*classLoader*/, java.lang.String.class);
+        in.readList(underlyingIfaces, null /*classLoader*/);
         mUnderlyingIfaces = Collections.unmodifiableList(underlyingIfaces);
     }
 
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
index 97281ed..3273e88 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
@@ -44,6 +44,7 @@
 import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
 import static android.net.TrafficStats.KB_IN_BYTES;
 import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.net.TrafficStats.UID_TETHERING;
 import static android.net.TrafficStats.UNSUPPORTED;
 import static android.os.Trace.TRACE_TAG_NETWORK;
 import static android.provider.Settings.Global.NETSTATS_AUGMENT_ENABLED;
@@ -70,7 +71,7 @@
 import static android.text.format.DateUtils.SECOND_IN_MILLIS;
 
 import static com.android.net.module.util.NetworkCapabilitiesUtils.getDisplayTransport;
-import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
+import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT;
 import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats;
 import static com.android.server.NetworkManagementSocketTagger.setKernelCounterSet;
 
@@ -89,7 +90,6 @@
 import android.database.ContentObserver;
 import android.net.DataUsageRequest;
 import android.net.INetd;
-import android.net.INetworkManagementEventObserver;
 import android.net.INetworkStatsService;
 import android.net.INetworkStatsSession;
 import android.net.Network;
@@ -106,6 +106,7 @@
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
 import android.net.TelephonyNetworkSpecifier;
+import android.net.TetherStatsParcel;
 import android.net.TetheringManager;
 import android.net.TrafficStats;
 import android.net.UnderlyingNetworkInfo;
@@ -120,12 +121,12 @@
 import android.os.HandlerExecutor;
 import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Messenger;
 import android.os.PowerManager;
 import android.os.RemoteException;
+import android.os.ServiceSpecificException;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -148,6 +149,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FileRotator;
+import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
 import com.android.net.module.util.BestClock;
 import com.android.net.module.util.BinderUtils;
 import com.android.net.module.util.CollectionUtils;
@@ -208,7 +210,6 @@
     private static final String TAG_NETSTATS_ERROR = "netstats_error";
 
     private final Context mContext;
-    private final INetworkManagementService mNetworkManager;
     private final NetworkStatsFactory mStatsFactory;
     private final AlarmManager mAlarmManager;
     private final Clock mClock;
@@ -223,6 +224,9 @@
     private final ContentObserver mContentObserver;
     private final ContentResolver mContentResolver;
 
+    protected INetd mNetd;
+    private final AlertObserver mAlertObserver = new AlertObserver();
+
     @VisibleForTesting
     public static final String ACTION_NETWORK_STATS_POLL =
             "com.android.server.action.NETWORK_STATS_POLL";
@@ -405,15 +409,16 @@
         }
     }
 
-    public static NetworkStatsService create(Context context,
-                INetworkManagementService networkManager) {
+    /** Creates a new NetworkStatsService */
+    public static NetworkStatsService create(Context context) {
         AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
         PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
         PowerManager.WakeLock wakeLock =
                 powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
         final INetd netd = INetd.Stub.asInterface(
                 (IBinder) context.getSystemService(Context.NETD_SERVICE));
-        final NetworkStatsService service = new NetworkStatsService(context, networkManager,
+        final NetworkStatsService service = new NetworkStatsService(context,
+                INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)),
                 alarmManager, wakeLock, getDefaultClock(),
                 new DefaultNetworkStatsSettings(context), new NetworkStatsFactory(netd),
                 new NetworkStatsObservers(), getDefaultSystemDir(), getDefaultBaseDir(),
@@ -426,14 +431,12 @@
     // This must not be called outside of tests, even within the same package, as this constructor
     // does not register the local service. Use the create() helper above.
     @VisibleForTesting
-    NetworkStatsService(Context context, INetworkManagementService networkManager,
-            AlarmManager alarmManager, PowerManager.WakeLock wakeLock, Clock clock,
-            NetworkStatsSettings settings, NetworkStatsFactory factory,
-            NetworkStatsObservers statsObservers, File systemDir, File baseDir,
-            @NonNull Dependencies deps) {
+    NetworkStatsService(Context context, INetd netd, AlarmManager alarmManager,
+            PowerManager.WakeLock wakeLock, Clock clock, NetworkStatsSettings settings,
+            NetworkStatsFactory factory, NetworkStatsObservers statsObservers, File systemDir,
+            File baseDir, @NonNull Dependencies deps) {
         mContext = Objects.requireNonNull(context, "missing Context");
-        mNetworkManager = Objects.requireNonNull(networkManager,
-                "missing INetworkManagementService");
+        mNetd = Objects.requireNonNull(netd, "missing Netd");
         mAlarmManager = Objects.requireNonNull(alarmManager, "missing AlarmManager");
         mClock = Objects.requireNonNull(clock, "missing Clock");
         mSettings = Objects.requireNonNull(settings, "missing NetworkStatsSettings");
@@ -506,6 +509,26 @@
                 new NetworkStatsManagerInternalImpl());
     }
 
+    /**
+     * Observer that watches for {@link INetdUnsolicitedEventListener} alerts.
+     */
+    @VisibleForTesting
+    public class AlertObserver extends BaseNetdUnsolicitedEventListener {
+        @Override
+        public void onQuotaLimitReached(@NonNull String alertName, @NonNull String ifName) {
+            PermissionUtils.enforceNetworkStackPermission(mContext);
+
+            if (LIMIT_GLOBAL_ALERT.equals(alertName)) {
+                // kick off background poll to collect network stats unless there is already
+                // such a call pending; UID stats are handled during normal polling interval.
+                if (!mHandler.hasMessages(MSG_PERFORM_POLL_REGISTER_ALERT)) {
+                    mHandler.sendEmptyMessageDelayed(MSG_PERFORM_POLL_REGISTER_ALERT,
+                            mSettings.getPollDelay());
+                }
+            }
+        }
+    }
+
     public void systemReady() {
         synchronized (mStatsLock) {
             mSystemReady = true;
@@ -551,9 +574,9 @@
         mContext.registerReceiver(mShutdownReceiver, shutdownFilter);
 
         try {
-            mNetworkManager.registerObserver(mAlertObserver);
-        } catch (RemoteException e) {
-            // ignored; service lives in system_server
+            mNetd.registerUnsolicitedEventListener(mAlertObserver);
+        } catch (RemoteException | ServiceSpecificException e) {
+            Log.wtf(TAG, "Error registering event listener :", e);
         }
 
         //  schedule periodic pall alarm based on {@link NetworkStatsSettings#getPollInterval()}.
@@ -641,13 +664,13 @@
     }
 
     /**
-     * Register for a global alert that is delivered through {@link INetworkManagementEventObserver}
+     * Register for a global alert that is delivered through {@link AlertObserver}
      * or {@link NetworkStatsProviderCallback#onAlertReached()} once a threshold amount of data has
      * been transferred.
      */
     private void registerGlobalAlert() {
         try {
-            mNetworkManager.setGlobalAlert(mGlobalAlertBytes);
+            mNetd.bandwidthSetGlobalAlert(mGlobalAlertBytes);
         } catch (IllegalStateException e) {
             Log.w(TAG, "problem registering for global alert: " + e);
         } catch (RemoteException e) {
@@ -766,8 +789,18 @@
                     }
                     return stats;
                 } catch (NullPointerException e) {
-                    // TODO: Track down and fix the cause of this crash and remove this catch block.
-                    Log.wtf(TAG, "NullPointerException in getSummaryForAllUid", e);
+                    throw e;
+                }
+            }
+
+            @Override
+            public NetworkStats getTaggedSummaryForAllUid(
+                    NetworkTemplate template, long start, long end) {
+                try {
+                    final NetworkStats tagStats = getUidTagComplete()
+                            .getSummary(template, start, end, mAccessLevel, mCallingUid);
+                    return tagStats;
+                } catch (NullPointerException e) {
                     throw e;
                 }
             }
@@ -922,6 +955,7 @@
 
     @Override
     public NetworkStats getDetailedUidStats(String[] requiredIfaces) {
+        enforceAnyPermissionOf(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
         try {
             final String[] ifacesToQuery =
                     mStatsFactory.augmentWithStackedInterfaces(requiredIfaces);
@@ -1226,26 +1260,6 @@
     };
 
     /**
-     * Observer that watches for {@link INetworkManagementService} alerts.
-     */
-    private final INetworkManagementEventObserver mAlertObserver = new BaseNetworkObserver() {
-        @Override
-        public void limitReached(String limitName, String iface) {
-            // only someone like NMS should be calling us
-            PermissionUtils.enforceNetworkStackPermission(mContext);
-
-            if (LIMIT_GLOBAL_ALERT.equals(limitName)) {
-                // kick off background poll to collect network stats unless there is already
-                // such a call pending; UID stats are handled during normal polling interval.
-                if (!mHandler.hasMessages(MSG_PERFORM_POLL_REGISTER_ALERT)) {
-                    mHandler.sendEmptyMessageDelayed(MSG_PERFORM_POLL_REGISTER_ALERT,
-                            mSettings.getPollDelay());
-                }
-            }
-        }
-    };
-
-    /**
      * Handle collapsed RAT type changed event.
      */
     @VisibleForTesting
@@ -1956,13 +1970,29 @@
      */
     // TODO: Remove this by implementing {@link NetworkStatsProvider} for non-offloaded
     //  tethering stats.
-    private NetworkStats getNetworkStatsTethering(int how) throws RemoteException {
+    private @NonNull NetworkStats getNetworkStatsTethering(int how) throws RemoteException {
+         // We only need to return per-UID stats. Per-device stats are already counted by
+        // interface counters.
+        if (how != STATS_PER_UID) {
+            return new NetworkStats(SystemClock.elapsedRealtime(), 0);
+        }
+
+        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
         try {
-            return mNetworkManager.getNetworkStatsTethering(how);
+            final TetherStatsParcel[] tetherStatsParcels = mNetd.tetherGetStats();
+            for (TetherStatsParcel tetherStats : tetherStatsParcels) {
+                try {
+                    stats.combineValues(new NetworkStats.Entry(tetherStats.iface, UID_TETHERING,
+                            SET_DEFAULT, TAG_NONE, tetherStats.rxBytes, tetherStats.rxPackets,
+                            tetherStats.txBytes, tetherStats.txPackets, 0L));
+                } catch (ArrayIndexOutOfBoundsException e) {
+                    throw new IllegalStateException("invalid tethering stats " + e);
+                }
+            }
         } catch (IllegalStateException e) {
             Log.wtf(TAG, "problem reading network stats", e);
-            return new NetworkStats(0L, 10);
         }
+        return stats;
     }
 
     // TODO: It is copied from ConnectivityService, consider refactor these check permission
@@ -2044,7 +2074,7 @@
 
         @NonNull final INetworkStatsProvider mProvider;
         @NonNull private final Semaphore mSemaphore;
-        @NonNull final INetworkManagementEventObserver mAlertObserver;
+        @NonNull final AlertObserver mAlertObserver;
         @NonNull final CopyOnWriteArrayList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList;
 
         @NonNull private final Object mProviderStatsLock = new Object();
@@ -2058,7 +2088,7 @@
         NetworkStatsProviderCallbackImpl(
                 @NonNull String tag, @NonNull INetworkStatsProvider provider,
                 @NonNull Semaphore semaphore,
-                @NonNull INetworkManagementEventObserver alertObserver,
+                @NonNull AlertObserver alertObserver,
                 @NonNull CopyOnWriteArrayList<NetworkStatsProviderCallbackImpl> cbList)
                 throws RemoteException {
             mTag = tag;
@@ -2106,7 +2136,7 @@
             // This binder object can only have been obtained by a process that holds
             // NETWORK_STATS_PROVIDER. Thus, no additional permission check is required.
             BinderUtils.withCleanCallingIdentity(() ->
-                    mAlertObserver.limitReached(LIMIT_GLOBAL_ALERT, null /* unused */));
+                    mAlertObserver.onQuotaLimitReached(LIMIT_GLOBAL_ALERT, null /* unused */));
         }
 
         @Override
diff --git a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
index 7a239af..068074a 100644
--- a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
+++ b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java
@@ -44,6 +44,7 @@
 
 import java.io.PrintWriter;
 import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /** Basic fused location provider implementation. */
 public class FusedLocationProvider extends LocationProviderBase {
@@ -69,6 +70,12 @@
     private final BroadcastReceiver mUserChangeReceiver;
 
     @GuardedBy("mLock")
+    boolean mGpsPresent;
+
+    @GuardedBy("mLock")
+    boolean mNlpPresent;
+
+    @GuardedBy("mLock")
     private ProviderRequest mRequest;
 
     @GuardedBy("mLock")
@@ -119,19 +126,28 @@
 
     @Override
     public void onFlush(OnFlushCompleteCallback callback) {
-        OnFlushCompleteCallback wrapper = new OnFlushCompleteCallback() {
-            private int mFlushCount = 2;
+        synchronized (mLock) {
+            AtomicInteger flushCount = new AtomicInteger(0);
+            if (mGpsPresent) {
+                flushCount.incrementAndGet();
+            }
+            if (mNlpPresent) {
+                flushCount.incrementAndGet();
+            }
 
-            @Override
-            public void onFlushComplete() {
-                if (--mFlushCount == 0) {
+            OnFlushCompleteCallback wrapper = () -> {
+                if (flushCount.decrementAndGet() == 0) {
                     callback.onFlushComplete();
                 }
-            }
-        };
+            };
 
-        mGpsListener.flush(wrapper);
-        mNetworkListener.flush(wrapper);
+            if (mGpsPresent) {
+                mGpsListener.flush(wrapper);
+            }
+            if (mNlpPresent) {
+                mNetworkListener.flush(wrapper);
+            }
+        }
     }
 
     @Override
@@ -139,9 +155,19 @@
 
     @GuardedBy("mLock")
     private void updateRequirementsLocked() {
-        long gpsInterval = mRequest.getQuality() < QUALITY_LOW_POWER ? mRequest.getIntervalMillis()
-                : INTERVAL_DISABLED;
-        long networkInterval = mRequest.getIntervalMillis();
+        // it's possible there might be race conditions on device start where a provider doesn't
+        // appear to be present yet, but once a provider is present it shouldn't go away.
+        if (!mGpsPresent) {
+            mGpsPresent = mLocationManager.hasProvider(GPS_PROVIDER);
+        }
+        if (!mNlpPresent) {
+            mNlpPresent = mLocationManager.hasProvider(NETWORK_PROVIDER);
+        }
+
+        long gpsInterval =
+                mGpsPresent && (!mNlpPresent || mRequest.getQuality() < QUALITY_LOW_POWER)
+                        ? mRequest.getIntervalMillis() : INTERVAL_DISABLED;
+        long networkInterval = mNlpPresent ? mRequest.getIntervalMillis() : INTERVAL_DISABLED;
 
         mGpsListener.resetProviderRequest(gpsInterval);
         mNetworkListener.resetProviderRequest(networkInterval);
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
index c206903..3f2b8ac 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
@@ -43,4 +43,8 @@
     <color name="settingslib_accent_primary_variant">@android:color/system_accent1_300</color>
 
     <color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_50</color>
+
+    <color name="settingslib_text_color_secondary_device_default">@android:color/system_neutral2_200</color>
+
+    <color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_100</color>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
index 0401098..ec3c336 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
@@ -65,4 +65,8 @@
     <color name="settingslib_background_device_default_light">@android:color/system_neutral1_50</color>
 
     <color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_900</color>
+
+    <color name="settingslib_text_color_secondary_device_default">@android:color/system_neutral2_700</color>
+
+    <color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_600</color>
 </resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml
index 1c33f1a..11546c8 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml
@@ -20,4 +20,9 @@
     <dimen name="app_icon_min_width">52dp</dimen>
     <dimen name="settingslib_preferred_minimum_touch_target">48dp</dimen>
     <dimen name="settingslib_dialogCornerRadius">28dp</dimen>
+
+    <!-- Left padding of the preference -->
+    <dimen name="settingslib_listPreferredItemPaddingStart">24dp</dimen>
+    <!-- Right padding of the preference -->
+    <dimen name="settingslib_listPreferredItemPaddingEnd">24dp</dimen>
 </resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
index 5800636..8e7226b 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
@@ -17,13 +17,19 @@
 <resources>
     <style name="TextAppearance.PreferenceTitle.SettingsLib"
            parent="@android:style/TextAppearance.Material.Subhead">
+        <item name="android:textColor">@color/settingslib_text_color_primary_device_default</item>
         <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
         <item name="android:textSize">20sp</item>
     </style>
 
+    <style name="TextAppearance.PreferenceSummary.SettingsLib"
+           parent="@android:style/TextAppearance.DeviceDefault.Small">
+        <item name="android:textColor">@color/settingslib_text_color_secondary_device_default</item>
+    </style>
+
     <style name="TextAppearance.CategoryTitle.SettingsLib"
            parent="@android:style/TextAppearance.DeviceDefault.Medium">
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textColor">@color/settingslib_text_color_preference_category_title</item>
         <item name="android:textSize">14sp</item>
     </style>
 
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
index 6bf288b..7bf75bc 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
@@ -19,8 +19,8 @@
     <!-- Only using in Settings application -->
     <style name="Theme.SettingsBase" parent="@android:style/Theme.DeviceDefault.Settings" >
         <item name="android:textAppearanceListItem">@style/TextAppearance.PreferenceTitle.SettingsLib</item>
-        <item name="android:listPreferredItemPaddingStart">24dp</item>
-        <item name="android:listPreferredItemPaddingLeft">24dp</item>
+        <item name="android:listPreferredItemPaddingStart">@dimen/settingslib_listPreferredItemPaddingStart</item>
+        <item name="android:listPreferredItemPaddingLeft">@dimen/settingslib_listPreferredItemPaddingStart</item>
         <item name="android:listPreferredItemPaddingEnd">16dp</item>
         <item name="android:listPreferredItemPaddingRight">16dp</item>
         <item name="preferenceTheme">@style/PreferenceTheme.SettingsLib</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/config.xml b/packages/SettingsLib/SettingsTheme/res/values/config.xml
new file mode 100644
index 0000000..e73dcc0
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values/config.xml
@@ -0,0 +1,19 @@
+<?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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <bool name="settingslib_config_icon_space_reserved">true</bool>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml
index 18af1f9..f7e0144 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/dimens.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml
@@ -21,4 +21,11 @@
     <dimen name="app_icon_min_width">56dp</dimen>
     <dimen name="two_target_min_width">72dp</dimen>
     <dimen name="settingslib_dialogCornerRadius">8dp</dimen>
+
+    <!-- Left padding of the preference -->
+    <dimen name="settingslib_listPreferredItemPaddingStart">16dp</dimen>
+    <!-- Right padding of the preference -->
+    <dimen name="settingslib_listPreferredItemPaddingEnd">16dp</dimen>
+    <!-- Icon size of the preference -->
+    <dimen name="settingslib_preferenceIconSize">24dp</dimen>
 </resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/styles.xml b/packages/SettingsLib/SettingsTheme/res/values/styles.xml
new file mode 100644
index 0000000..aaab0f0
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values/styles.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.
+  -->
+<resources>
+    <style name="TextAppearance.PreferenceTitle.SettingsLib"
+           parent="@android:style/TextAppearance.Material.Subhead">
+    </style>
+
+    <style name="TextAppearance.PreferenceSummary.SettingsLib"
+           parent="@style/PreferenceSummaryTextStyle">
+    </style>
+
+    <style name="TextAppearance.CategoryTitle.SettingsLib"
+           parent="@android:style/TextAppearance.DeviceDefault.Medium">
+    </style>
+</resources>
diff --git a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
index de45ea5..d3fe4a7 100644
--- a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
+++ b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
@@ -47,7 +47,7 @@
  * Annotation processor for {@link SearchIndexable} that generates {@link SearchIndexableResources}
  * subclasses.
  */
-@SupportedSourceVersion(SourceVersion.RELEASE_8)
+@SupportedSourceVersion(SourceVersion.RELEASE_9)
 @SupportedAnnotationTypes({"com.android.settingslib.search.SearchIndexable"})
 public class IndexableProcessor extends AbstractProcessor {
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
index 9dd329e..b11bbde 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
@@ -138,13 +138,6 @@
         return mService.getActiveDevice();
     }
 
-    public boolean isAudioOn() {
-        if (mService == null) {
-            return false;
-        }
-        return mService.isAudioOn();
-    }
-
     public int getAudioState(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtils.java
index 21a4ac6..fa4ae67 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtils.java
@@ -60,6 +60,22 @@
         return true;
     }
 
+    /**
+     * Confirm Wi-Fi Config is allowed to add according to whether user restriction is set
+     *
+     * @param context A context
+     * @return whether the device is permitted to add new Wi-Fi config
+     */
+    public static boolean isAddWifiConfigAllowed(Context context) {
+        final UserManager userManager = context.getSystemService(UserManager.class);
+        final Bundle restrictions = userManager.getUserRestrictions();
+        if (isAtLeastT() && restrictions.getBoolean(UserManager.DISALLOW_ADD_WIFI_CONFIG)) {
+            Log.i(TAG, "Wi-Fi Add network isn't available due to user restriction.");
+            return false;
+        }
+        return true;
+    }
+
     @ChecksSdkIntAtLeast(api=Build.VERSION_CODES.TIRAMISU)
     private static boolean isAtLeastT() {
         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HeadsetProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HeadsetProfileTest.java
index 30182c4..f5ce664 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HeadsetProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HeadsetProfileTest.java
@@ -55,15 +55,6 @@
     }
 
     @Test
-    public void bluetoothProfile_shouldReturnTheAudioStatusFromBlueToothHeadsetService() {
-        when(mService.isAudioOn()).thenReturn(true);
-        assertThat(mProfile.isAudioOn()).isTrue();
-
-        when(mService.isAudioOn()).thenReturn(false);
-        assertThat(mProfile.isAudioOn()).isFalse();
-    }
-
-    @Test
     public void testHeadsetProfile_shouldReturnAudioState() {
         when(mService.getAudioState(mBluetoothDevice)).
                 thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtilsTest.java
index 3c339de..f6af09a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtilsTest.java
@@ -103,4 +103,30 @@
 
         assertThat(WifiEnterpriseRestrictionUtils.isWifiDirectAllowed(mContext)).isTrue();
     }
+
+    @Test
+    public void isAddWifiConfigAllowed_setSDKForS_shouldReturnTrue() {
+        ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.S);
+        when(mBundle.getBoolean(UserManager.DISALLOW_ADD_WIFI_CONFIG)).thenReturn(true);
+
+        assertThat(WifiEnterpriseRestrictionUtils.isAddWifiConfigAllowed(mContext)).isTrue();
+    }
+
+    @Test
+    public void isAddWifiConfigAllowed_setSDKForTAndDisallowForRestriction_shouldReturnFalse() {
+        ReflectionHelpers.setStaticField(
+            Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.TIRAMISU);
+        when(mBundle.getBoolean(UserManager.DISALLOW_ADD_WIFI_CONFIG)).thenReturn(true);
+
+        assertThat(WifiEnterpriseRestrictionUtils.isAddWifiConfigAllowed(mContext)).isFalse();
+    }
+
+    @Test
+    public void isAddWifiConfigAllowed_setSDKForTAndAllowForRestriction_shouldReturnTrue() {
+        ReflectionHelpers.setStaticField(
+            Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.TIRAMISU);
+        when(mBundle.getBoolean(UserManager.DISALLOW_ADD_WIFI_CONFIG)).thenReturn(false);
+
+        assertThat(WifiEnterpriseRestrictionUtils.isAddWifiConfigAllowed(mContext)).isTrue();
+    }
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 71accc4..00b5f50 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -78,6 +78,8 @@
         Settings.System.NOTIFICATION_SOUND,
         Settings.System.ACCELEROMETER_ROTATION,
         Settings.System.SHOW_BATTERY_PERCENT,
+        Settings.System.ALARM_VIBRATION_INTENSITY,
+        Settings.System.MEDIA_VIBRATION_INTENSITY,
         Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
         Settings.System.RING_VIBRATION_INTENSITY,
         Settings.System.HAPTIC_FEEDBACK_INTENSITY,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 84e9d28..6bcb769 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -118,6 +118,8 @@
         VALIDATORS.put(System.MUTE_STREAMS_AFFECTED, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(System.VIBRATE_ON, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.APPLY_RAMPING_RINGER, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.ALARM_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
+        VALIDATORS.put(System.MEDIA_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
         VALIDATORS.put(System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
         VALIDATORS.put(System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
         VALIDATORS.put(System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 00cdc9b..c5f027b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2916,6 +2916,18 @@
         dumpSetting(s, p,
                 Settings.System.VIBRATE_WHEN_RINGING,
                 SystemSettingsProto.Vibrate.WHEN_RINGING);
+
+        // NOTIFICATION_VIBRATION_INTENSITY is already logged at Notification.vibration_intensity
+        // HAPTIC_FEEDBACK_INTENSITY is already logged at HapticFeedback.intensity
+        dumpSetting(s, p,
+                Settings.System.ALARM_VIBRATION_INTENSITY,
+                SystemSettingsProto.Vibrate.ALARM_INTENSITY);
+        dumpSetting(s, p,
+                Settings.System.MEDIA_VIBRATION_INTENSITY,
+                SystemSettingsProto.Vibrate.MEDIA_INTENSITY);
+        dumpSetting(s, p,
+                Settings.System.RING_VIBRATION_INTENSITY,
+                SystemSettingsProto.Vibrate.RING_INTENSITY);
         p.end(vibrateToken);
 
         final long volumeToken = p.start(SystemSettingsProto.VOLUME);
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 1d4a73c..1303a62 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -23,6 +23,7 @@
         >
 
         <!-- Standard permissions granted to the shell. -->
+    <uses-permission android:name="android.permission.LAUNCH_DEVICE_MANAGER_SETUP" />
     <uses-permission android:name="android.permission.GET_RUNTIME_PERMISSIONS" />
     <uses-permission android:name="android.permission.SEND_SMS" />
     <uses-permission android:name="android.permission.READ_SMS" />
@@ -349,6 +350,9 @@
     <!-- Permission required for CTS test - CrossProfileAppsHostSideTest -->
     <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
 
+    <!-- Permission required for CTS test - CrossProfileAppsHostSideTest -->
+    <uses-permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES"/>
+
     <!-- permissions required for CTS test - PhoneStateListenerTest -->
     <uses-permission android:name="android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH" />
 
@@ -607,11 +611,14 @@
     <!-- Permission required for ATS test - CarDevicePolicyManagerTest -->
     <uses-permission android:name="android.permission.LOCK_DEVICE" />
 
-    <!-- Permission required for CTS test - CtsSafetyCenterTestCases -->
+    <!-- Permissions required for CTS test - CtsSafetyCenterTestCases -->
     <uses-permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
-
-    <!-- Permission required for CTS test - CtsSafetyCenterTestCases -->
     <uses-permission android:name="android.permission.READ_SAFETY_CENTER_STATUS" />
+    <uses-permission android:name="android.permission.MANAGE_SAFETY_CENTER" />
+
+
+    <!-- Permission required for CTS test - Notification test suite -->
+    <uses-permission android:name="android.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL" />
 
     <application android:label="@string/app_label"
                 android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/Shell/res/values-watch/strings.xml b/packages/Shell/res/values-watch/strings.xml
new file mode 100644
index 0000000..5f7bfcb
--- /dev/null
+++ b/packages/Shell/res/values-watch/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+  <!-- Title for Bug report notification indicating the number of the bug report and the
+       percentage complete. Example: "Bug report #3 is 20% complete" [CHAR LIMIT=50] -->
+    <string name="bugreport_in_progress_title">Bug report <xliff:g id="id" example="#3">#%1$d</xliff:g> is <xliff:g id="percentage" example="20%">%2$s</xliff:g> complete</string>
+</resources>
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index ee9d430..c5a01a1 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -199,6 +199,15 @@
      */
     private static final String BUGREPORT_DIR = "bugreports";
 
+    /**
+     * The directory in which System Trace files from the native System Tracing app are stored for
+     * Wear devices.
+     */
+    private static final String WEAR_SYSTEM_TRACES_DIRECTORY_ON_DEVICE = "data/local/traces/";
+
+    /** The directory that contains System Traces in bugreports that include System Traces. */
+    private static final String WEAR_SYSTEM_TRACES_DIRECTORY_IN_BUGREPORT = "systraces/";
+
     private static final String NOTIFICATION_CHANNEL_ID = "bugreports";
 
     /**
@@ -724,14 +733,16 @@
         nf.setMaximumFractionDigits(2);
         final String percentageText = nf.format((double) info.progress.intValue() / 100);
 
-        String title = mContext.getString(R.string.bugreport_in_progress_title, info.id);
-
-        // TODO: Remove this workaround when notification progress is implemented on Wear.
+        final String title;
         if (mIsWatch) {
+            // TODO: Remove this workaround when notification progress is implemented on Wear.
             nf.setMinimumFractionDigits(0);
             nf.setMaximumFractionDigits(0);
             final String watchPercentageText = nf.format((double) info.progress.intValue() / 100);
-            title = title + "\n" + watchPercentageText;
+            title = mContext.getString(
+                R.string.bugreport_in_progress_title, info.id, watchPercentageText);
+        } else {
+            title = mContext.getString(R.string.bugreport_in_progress_title, info.id);
         }
 
         final String name =
@@ -1456,6 +1467,16 @@
         }
     }
 
+    /** Returns an array of the system trace files collected by the System Tracing native app. */
+    private static File[] getSystemTraceFiles() {
+        try {
+            return new File(WEAR_SYSTEM_TRACES_DIRECTORY_ON_DEVICE).listFiles();
+        } catch (SecurityException e) {
+            Log.e(TAG, "Error getting system trace files.", e);
+            return new File[]{};
+        }
+    }
+
     /**
      * Adds the user-provided info into the bugreport zip file.
      * <p>
@@ -1475,8 +1496,17 @@
             Log.wtf(TAG, "addDetailsToZipFile(): no bugreportFile on " + info);
             return;
         }
-        if (TextUtils.isEmpty(info.getTitle()) && TextUtils.isEmpty(info.getDescription())) {
-            Log.d(TAG, "Not touching zip file since neither title nor description are set");
+
+        File[] systemTracesToIncludeInBugreport = new File[] {};
+        if (mIsWatch) {
+            systemTracesToIncludeInBugreport = getSystemTraceFiles();
+            Log.d(TAG, "Found " + systemTracesToIncludeInBugreport.length + " system traces.");
+        }
+
+        if (TextUtils.isEmpty(info.getTitle())
+                    && TextUtils.isEmpty(info.getDescription())
+                    && systemTracesToIncludeInBugreport.length == 0) {
+            Log.d(TAG, "Not touching zip file: no detail to add.");
             return;
         }
         if (info.addedDetailsToZip || info.addingDetailsToZip) {
@@ -1487,7 +1517,10 @@
 
         // It's not possible to add a new entry into an existing file, so we need to create a new
         // zip, copy all entries, then rename it.
-        sendBugreportBeingUpdatedNotification(mContext, info.id); // ...and that takes time
+        if (!mIsWatch) {
+            // TODO(b/184854609): re-introduce this notification for Wear.
+            sendBugreportBeingUpdatedNotification(mContext, info.id); // ...and that takes time
+        }
 
         final File dir = info.bugreportFile.getParentFile();
         final File tmpZip = new File(dir, "tmp-" + info.bugreportFile.getName());
@@ -1508,6 +1541,13 @@
             }
 
             // Then add the user-provided info.
+            if (systemTracesToIncludeInBugreport.length != 0) {
+                for (File trace : systemTracesToIncludeInBugreport) {
+                    addEntry(zos,
+                            WEAR_SYSTEM_TRACES_DIRECTORY_IN_BUGREPORT + trace.getName(),
+                            new FileInputStream(trace));
+                }
+            }
             addEntry(zos, "title.txt", info.getTitle());
             addEntry(zos, "description.txt", info.getDescription());
         } catch (IOException e) {
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index c805e2d..137a1fd 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -129,8 +129,15 @@
 }
 
 filegroup {
+    name: "AAA-src",
+    srcs: ["tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java"],
+    path: "tests/src",
+}
+
+filegroup {
     name: "SystemUI-tests-utils",
     srcs: [
+        "tests/src/com/android/systemui/SysuiBaseFragmentTest.java",
         "tests/src/com/android/systemui/SysuiTestCase.java",
         "tests/src/com/android/systemui/TestableDependency.java",
         "tests/src/com/android/systemui/classifier/FalsingManagerFake.java",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index e907efb..e9e85f1 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -299,6 +299,9 @@
 
     <uses-permission android:name="android.permission.BIND_APPWIDGET" />
 
+    <!-- For clipboard overlay -->
+    <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" />
+
     <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" />
@@ -348,6 +351,7 @@
         <!-- started from PhoneWindowManager
              TODO: Should have an android:permission attribute -->
         <service android:name=".screenshot.TakeScreenshotService"
+            android:permission="com.android.systemui.permission.SELF"
             android:process=":screenshot"
             android:exported="false" />
 
@@ -760,6 +764,12 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".clipboardoverlay.EditTextActivity"
+                  android:theme="@style/EditTextActivity"
+                  android:exported="false"
+                  android:excludeFromRecents="true"
+                  />
+
         <activity android:name=".controls.management.ControlsProviderSelectorActivity"
                   android:label="@string/controls_providers_title"
                   android:theme="@style/Theme.ControlsManagement"
@@ -845,6 +855,12 @@
             android:singleUser="true"
             android:permission="android.permission.BIND_DREAM_SERVICE" />
 
+        <!-- Service for external clients to do media transfer -->
+        <!-- TODO(b/203800643): Export and guard with a permission. -->
+        <service
+            android:name=".media.taptotransfer.sender.MediaTttSenderService"
+           />
+
         <receiver
             android:name=".tuner.TunerService$ClearReceiver"
             android:exported="false">
diff --git a/packages/SystemUI/res-keyguard/values/bools.xml b/packages/SystemUI/res-keyguard/values/bools.xml
index 2b83787..c5bf4ce 100644
--- a/packages/SystemUI/res-keyguard/values/bools.xml
+++ b/packages/SystemUI/res-keyguard/values/bools.xml
@@ -17,4 +17,5 @@
 <resources>
     <bool name="kg_show_ime_at_screen_on">true</bool>
     <bool name="kg_use_all_caps">true</bool>
+    <bool name="flag_active_unlock">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/drawable/ic_baseline_devices_24.xml b/packages/SystemUI/res/drawable/ic_baseline_devices_24.xml
new file mode 100644
index 0000000..61c32b22
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_baseline_devices_24.xml
@@ -0,0 +1,27 @@
+<!--
+  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
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M4,6h18L22,4L4,4c-1.1,0 -2,0.9 -2,2v11L0,17v3h14v-3L4,17L4,6zM23,8h-6c-0.55,0 -1,0.45 -1,1v10c0,0.55 0.45,1 1,1h6c0.55,0 1,-0.45 1,-1L24,9c0,-0.55 -0.45,-1 -1,-1zM22,17h-4v-7h4v7z"/>
+</vector>
+
diff --git a/packages/SystemUI/res/layout/clipboard_content_preview.xml b/packages/SystemUI/res/layout/clipboard_content_preview.xml
new file mode 100644
index 0000000..7317a94
--- /dev/null
+++ b/packages/SystemUI/res/layout/clipboard_content_preview.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             xmlns:app="http://schemas.android.com/apk/res-auto"
+             android:id="@+id/preview_border"
+             android:elevation="9dp"
+             android:layout_width="wrap_content"
+             android:layout_height="wrap_content"
+             android:layout_marginStart="@dimen/screenshot_offset_x"
+             android:layout_marginBottom="@dimen/screenshot_offset_y"
+             android:layout_gravity="bottom|start"
+             app:layout_constraintStart_toStartOf="parent"
+             app:layout_constraintBottom_toBottomOf="parent"
+             android:clipToPadding="false"
+             android:clipChildren="false"
+             android:padding="4dp"
+             android:background="@drawable/screenshot_border"
+             >
+    <FrameLayout
+        android:elevation="0dp"
+        android:background="@drawable/screenshot_preview_background"
+        android:clipChildren="true"
+        android:clipToOutline="true"
+        android:clipToPadding="true"
+        android:layout_width="@dimen/screenshot_x_scale"
+        android:layout_height="wrap_content">
+        <TextView android:id="@+id/text_preview"
+                  android:textFontWeight="500"
+                  android:padding="8dp"
+                  android:gravity="center|start"
+                  android:ellipsize="end"
+                  android:autoSizeTextType="uniform"
+                  android:autoSizeMinTextSize="10sp"
+                  android:autoSizeMaxTextSize="200sp"
+                  android:textColor="?android:attr/textColorPrimary"
+                  android:layout_width="@dimen/screenshot_x_scale"
+                  android:layout_height="@dimen/screenshot_x_scale"/>
+        <ImageView
+            android:id="@+id/image_preview"
+            android:scaleType="fitCenter"
+            android:adjustViewBounds="true"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+    </FrameLayout>
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml
new file mode 100644
index 0000000..8f6753a
--- /dev/null
+++ b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <Button
+        android:id="@+id/copy_button"
+        style="@android:style/Widget.DeviceDefault.Button.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="8dp"
+        android:layout_marginTop="8dp"
+        android:text="@string/clipboard_edit_text_copy"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/attribution"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        app:layout_constraintStart_toStartOf="@+id/copy_button"
+        app:layout_constraintTop_toBottomOf="@+id/copy_button" />
+
+    <ImageButton
+        android:id="@+id/share"
+        style="@android:style/Widget.Material.Button.Borderless"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:layout_marginEnd="8dp"
+        android:padding="12dp"
+        android:scaleType="fitCenter"
+        android:contentDescription="@*android:string/share"
+        android:tooltipText="@*android:string/share"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@+id/copy_button"
+        android:src="@drawable/ic_screenshot_share" />
+
+    <ScrollView
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginTop="8dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintStart_toStartOf="@+id/copy_button"
+        app:layout_constraintTop_toBottomOf="@+id/attribution">
+
+        <EditText
+            android:id="@+id/edit_text"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="start|top"
+            android:textSize="24sp" />
+    </ScrollView>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
new file mode 100644
index 0000000..76280d8
--- /dev/null
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<com.android.systemui.clipboardoverlay.DraggableConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_gravity="bottom"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+    <ImageView
+        android:id="@+id/actions_container_background"
+        android:visibility="gone"
+        android:layout_height="0dp"
+        android:layout_width="0dp"
+        android:elevation="1dp"
+        android:background="@drawable/action_chip_container_background"
+        android:layout_marginStart="@dimen/screenshot_action_container_margin_horizontal"
+        app:layout_constraintBottom_toBottomOf="@+id/actions_container"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="@+id/actions_container"
+        app:layout_constraintEnd_toEndOf="@+id/actions_container"/>
+    <HorizontalScrollView
+        android:id="@+id/actions_container"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/screenshot_action_container_margin_horizontal"
+        android:paddingEnd="@dimen/screenshot_action_container_padding_right"
+        android:paddingVertical="@dimen/screenshot_action_container_padding_vertical"
+        android:elevation="1dp"
+        android:scrollbars="none"
+        app:layout_constraintHorizontal_bias="0"
+        app:layout_constraintWidth_percent="1.0"
+        app:layout_constraintWidth_max="wrap"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/preview_border"
+        app:layout_constraintEnd_toEndOf="parent">
+        <LinearLayout
+            android:id="@+id/actions"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content">
+            <include layout="@layout/screenshot_action_chip"
+                     android:id="@+id/remote_copy_chip"/>
+            <include layout="@layout/screenshot_action_chip"
+                     android:id="@+id/edit_chip"/>
+        </LinearLayout>
+    </HorizontalScrollView>
+    <include layout="@layout/clipboard_content_preview" />
+</com.android.systemui.clipboardoverlay.DraggableConstraintLayout>
diff --git a/packages/SystemUI/res/layout/contaminant_dialog.xml b/packages/SystemUI/res/layout/contaminant_dialog.xml
index ea6d900..5f8c305 100644
--- a/packages/SystemUI/res/layout/contaminant_dialog.xml
+++ b/packages/SystemUI/res/layout/contaminant_dialog.xml
@@ -63,7 +63,8 @@
 
         <TextView
             android:id="@+id/learnMore"
-            style="@style/USBContaminant.UserAction" />
+            style="@style/USBContaminant.UserAction"
+            android:visibility="gone" />
 
         <View
             android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index cb963e6..1f815b7 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -38,6 +38,15 @@
         <item name="android:textColorSecondary">?android:attr/textColorPrimaryInverse</item>
     </style>
 
+    <!-- Clipboard overlay's edit text activity. -->
+    <style name="EditTextActivity" parent="@android:style/Theme.DeviceDefault.DayNight">
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowLightStatusBar">false</item>
+        <item name="android:windowLightNavigationBar">false</item>
+        <item name="android:navigationBarColor">?android:attr/colorBackgroundFloating</item>
+        <item name="android:textColorSecondary">?android:attr/textColorPrimaryInverse</item>
+    </style>
+
     <style name="Screenshot" parent="@android:style/Theme.DeviceDefault.DayNight">
         <item name="android:textColorPrimary">?android:attr/textColorPrimaryInverse</item>
     </style>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 7b8f349..9c35fea 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -294,6 +294,7 @@
     <string-array name="config_systemUIServiceComponents" translatable="false">
         <item>com.android.systemui.util.NotificationChannels</item>
         <item>com.android.systemui.keyguard.KeyguardViewMediator</item>
+        <item>com.android.keyguard.KeyguardBiometricLockoutLogger</item>
         <item>com.android.systemui.recents.Recents</item>
         <item>com.android.systemui.volume.VolumeUI</item>
         <item>com.android.systemui.statusbar.phone.StatusBar</item>
@@ -315,6 +316,7 @@
         <item>com.android.systemui.accessibility.SystemActions</item>
         <item>com.android.systemui.toast.ToastUI</item>
         <item>com.android.systemui.wmshell.WMShell</item>
+        <item>com.android.systemui.clipboardoverlay.ClipboardListener</item>
     </string-array>
 
     <!-- SystemUI Services: The classes of the additional stuff to start. Services here are
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2916c1c..08fb2c6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1165,7 +1165,7 @@
     <string name="wallet_lockscreen_settings_label">Lock screen settings</string>
 
     <!-- QR Code Scanner label, title [CHAR LIMIT=32] -->
-    <string name="qr_code_scanner_title">QR Code</string>
+    <string name="qr_code_scanner_title">QR code</string>
 
     <!-- QR Code Scanner description [CHAR LIMIT=NONE] -->
     <string name="qr_code_scanner_description">Tap to scan</string>
@@ -2152,8 +2152,8 @@
     <!--- ****** Media tap-to-transfer ****** -->
     <!-- Text for a button to undo the media transfer. [CHAR LIMIT=20] -->
     <string name="media_transfer_undo">Undo</string>
-    <!-- Text to ask the user to move their device closer to a different device (deviceName) in order to play music on the different device. [CHAR LIMIT=75] -->
-    <string name="media_move_closer_to_transfer">Move closer to play on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
+    <!-- Text to ask the user to move their device closer to a different device (deviceName) in order to play media on the different device. [CHAR LIMIT=75] -->
+    <string name="media_move_closer_to_start_cast">Move closer to play on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
     <!-- Text informing the user that their media is now playing on a different device (deviceName). [CHAR LIMIT=50] -->
     <string name="media_transfer_playing">Playing on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
 
@@ -2349,4 +2349,9 @@
     <string name="fgs_manager_dialog_title">Apps running in the background</string>
     <!-- Label of the button to stop the app from running in the background [CHAR LIMIT=12]-->
     <string name="fgs_manager_app_item_stop_button_label">Stop</string>
+
+    <!-- Label for button to copy edited text back to the clipboard [CHAR LIMIT=20] -->
+    <string name="clipboard_edit_text_copy">Copy</string>
+    <!-- Text informing user that content has been copied to the system clipboard [CHAR LIMIT=NONE] -->
+    <string name="clipboard_overlay_text_copied">Copied</string>
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 18bfb52..1b52811 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -666,6 +666,14 @@
 
     <style name="Screenshot" parent="@android:style/Theme.DeviceDefault.DayNight"/>
 
+    <!-- Clipboard overlay's edit text activity. -->
+    <style name="EditTextActivity" parent="@android:style/Theme.DeviceDefault.DayNight">
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowLightStatusBar">true</item>
+        <item name="android:windowLightNavigationBar">true</item>
+        <item name="android:navigationBarColor">?android:attr/colorBackgroundFloating</item>
+    </style>
+
     <!-- Privacy dialog -->
     <style name="PrivacyDialog" parent="Theme.SystemUI.QuickSettings.Dialog">
         <item name="android:windowIsTranslucent">true</item>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
new file mode 100644
index 0000000..861a4ed
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.shared.mediattt;
+
+parcelable DeviceInfo;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.kt
new file mode 100644
index 0000000..d41aaf3
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/DeviceInfo.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.shared.mediattt
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * Represents a device that can send or receive media. Includes any device information necessary for
+ * SysUI to display an informative chip to the user.
+ */
+class DeviceInfo(val name: String) : Parcelable {
+    constructor(parcel: Parcel) : this(parcel.readString())
+
+    override fun writeToParcel(dest: Parcel?, flags: Int) {
+        dest?.writeString(name)
+    }
+
+    override fun describeContents() = 0
+
+    override fun toString() = "name: $name"
+
+    companion object CREATOR : Parcelable.Creator<DeviceInfo> {
+        override fun createFromParcel(parcel: Parcel) = DeviceInfo(parcel)
+        override fun newArray(size: Int) = arrayOfNulls<DeviceInfo?>(size)
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl
new file mode 100644
index 0000000..484791d
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl
@@ -0,0 +1,46 @@
+/*
+ * 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.shared.mediattt;
+
+import android.media.MediaRoute2Info;
+import com.android.systemui.shared.mediattt.DeviceInfo;
+
+/**
+ * A callback interface that can be invoked to trigger media transfer events on System UI.
+ *
+ * This interface is for the *sender* device, which is the device currently playing media. This
+ * sender device can transfer the media to a different device, called the receiver.
+ *
+ * System UI will implement this interface and other services will invoke it.
+ */
+interface IDeviceSenderCallback {
+    /**
+     * Invoke to notify System UI that this device (the sender) is close to a receiver device, so
+     * the user can potentially *start* a cast to the receiver device if the user moves their device
+     * a bit closer.
+     *
+     * Important notes:
+     *   - When this callback triggers, the device is close enough to inform the user that
+     *     transferring is an option, but the device is *not* close enough to actually initiate a
+     *     transfer yet.
+     *   - This callback is for *starting* a cast. It should be used when this device is currently
+     *     playing media locally and the media should be transferred to be played on the receiver
+     *     device instead.
+     */
+    oneway void closeToReceiverToStartCast(
+        in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
index 9808374..1d2caf9 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
@@ -28,6 +28,8 @@
 import android.view.ViewRootImpl;
 import android.view.ViewTreeObserver;
 
+import androidx.annotation.VisibleForTesting;
+
 import java.io.PrintWriter;
 import java.util.concurrent.Executor;
 
@@ -60,6 +62,7 @@
     private final Rect mRegisteredSamplingBounds = new Rect();
     private final SamplingCallback mCallback;
     private final Executor mBackgroundExecutor;
+    private final SysuiCompositionSamplingListener mCompositionSamplingListener;
     private boolean mSamplingEnabled = false;
     private boolean mSamplingListenerRegistered = false;
 
@@ -91,9 +94,17 @@
 
     public RegionSamplingHelper(View sampledView, SamplingCallback samplingCallback,
             Executor backgroundExecutor) {
+        this(sampledView, samplingCallback, sampledView.getContext().getMainExecutor(),
+                backgroundExecutor, new SysuiCompositionSamplingListener());
+    }
+
+    @VisibleForTesting
+    RegionSamplingHelper(View sampledView, SamplingCallback samplingCallback,
+            Executor mainExecutor, Executor backgroundExecutor,
+            SysuiCompositionSamplingListener compositionSamplingListener) {
         mBackgroundExecutor = backgroundExecutor;
-        mSamplingListener = new CompositionSamplingListener(
-                sampledView.getContext().getMainExecutor()) {
+        mCompositionSamplingListener = compositionSamplingListener;
+        mSamplingListener = new CompositionSamplingListener(mainExecutor) {
             @Override
             public void onSampleCollected(float medianLuma) {
                 if (mSamplingEnabled) {
@@ -136,7 +147,7 @@
 
     public void stopAndDestroy() {
         stop();
-        mSamplingListener.destroy();
+        mBackgroundExecutor.execute(mSamplingListener::destroy);
         mIsDestroyed = true;
     }
 
@@ -189,13 +200,12 @@
                 // We only want to re-register if something actually changed
                 unregisterSamplingListener();
                 mSamplingListenerRegistered = true;
-                SurfaceControl wrappedStopLayer = stopLayerControl == null
-                        ? null : new SurfaceControl(stopLayerControl, "regionSampling");
+                SurfaceControl wrappedStopLayer = wrap(stopLayerControl);
                 mBackgroundExecutor.execute(() -> {
                     if (wrappedStopLayer != null && !wrappedStopLayer.isValid()) {
                         return;
                     }
-                    CompositionSamplingListener.register(mSamplingListener, DEFAULT_DISPLAY,
+                    mCompositionSamplingListener.register(mSamplingListener, DEFAULT_DISPLAY,
                             wrappedStopLayer, mSamplingRequestBounds);
                 });
                 mRegisteredSamplingBounds.set(mSamplingRequestBounds);
@@ -208,14 +218,21 @@
         }
     }
 
+    @VisibleForTesting
+    protected SurfaceControl wrap(SurfaceControl stopLayerControl) {
+        return stopLayerControl == null ? null : new SurfaceControl(stopLayerControl,
+                "regionSampling");
+    }
+
     private void unregisterSamplingListener() {
         if (mSamplingListenerRegistered) {
             mSamplingListenerRegistered = false;
             SurfaceControl wrappedStopLayer = mWrappedStopLayer;
             mRegisteredStopLayer = null;
+            mWrappedStopLayer = null;
             mRegisteredSamplingBounds.setEmpty();
             mBackgroundExecutor.execute(() -> {
-                CompositionSamplingListener.unregister(mSamplingListener);
+                mCompositionSamplingListener.unregister(mSamplingListener);
                 if (wrappedStopLayer != null && wrappedStopLayer.isValid()) {
                     wrappedStopLayer.release();
                 }
@@ -299,4 +316,19 @@
             return true;
         }
     }
+
+    @VisibleForTesting
+    public static class SysuiCompositionSamplingListener {
+        public void register(CompositionSamplingListener listener,
+                int displayId, SurfaceControl stopLayer, Rect samplingArea) {
+            CompositionSamplingListener.register(listener, displayId, stopLayer, samplingArea);
+        }
+
+        /**
+         * Unregisters a sampling listener.
+         */
+        public void unregister(CompositionSamplingListener listener) {
+            CompositionSamplingListener.unregister(listener);
+        }
+    }
 }
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 d182399..54c798c 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
@@ -60,8 +60,6 @@
     // See IRecentTasks.aidl
     public static final String KEY_EXTRA_RECENT_TASKS = "recent_tasks";
 
-    public static final String NAV_BAR_MODE_2BUTTON_OVERLAY =
-            WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY;
     public static final String NAV_BAR_MODE_3BUTTON_OVERLAY =
             WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
     public static final String NAV_BAR_MODE_GESTURAL_OVERLAY =
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
index e84b552..8d26ddd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
@@ -205,8 +205,10 @@
                     @Override
                     @SuppressLint("NewApi")
                     public void run() {
-                        counterLauncher.cleanUp(info.getRootLeash());
-                        counterWallpaper.cleanUp(info.getRootLeash());
+                        final SurfaceControl.Transaction finishTransaction =
+                                new SurfaceControl.Transaction();
+                        counterLauncher.cleanUp(finishTransaction);
+                        counterWallpaper.cleanUp(finishTransaction);
                         // Release surface references now. This is apparently to free GPU memory
                         // while doing quick operations (eg. during CTS).
                         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
@@ -216,7 +218,7 @@
                             leashMap.valueAt(i).release();
                         }
                         try {
-                            finishCallback.onTransitionFinished(null /* wct */, null /* sct */);
+                            finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
                         } catch (RemoteException e) {
                             Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
                                     + " finished callback", e);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
index 22698a8..a274b74 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
@@ -89,6 +89,7 @@
 
     override fun destroy() {
         source?.removeCallback(this)
+        source?.destroy()
     }
 
     override fun onTransitionStarted() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
new file mode 100644
index 0000000..214b284
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
@@ -0,0 +1,176 @@
+/*
+ * 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.keyguard
+
+import android.content.Context
+import android.hardware.biometrics.BiometricSourceType
+import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
+import com.android.keyguard.KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/**
+ * Logs events when primary authentication requirements change. Primary authentication is considered
+ * authentication using pin/pattern/password input.
+ *
+ * See [PrimaryAuthRequiredEvent] for all the events and their descriptions.
+ */
+@SysUISingleton
+class KeyguardBiometricLockoutLogger @Inject constructor(
+    context: Context?,
+    private val uiEventLogger: UiEventLogger,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val dumpManager: DumpManager
+) : CoreStartable(context) {
+    private var fingerprintLockedOut = false
+    private var faceLockedOut = false
+    private var encryptedOrLockdown = false
+    private var unattendedUpdate = false
+    private var timeout = false
+
+    override fun start() {
+        dumpManager.registerDumpable(this)
+        mKeyguardUpdateMonitorCallback.onStrongAuthStateChanged(
+                KeyguardUpdateMonitor.getCurrentUser())
+        keyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback)
+    }
+
+    private val mKeyguardUpdateMonitorCallback: KeyguardUpdateMonitorCallback =
+            object : KeyguardUpdateMonitorCallback() {
+        override fun onLockedOutStateChanged(biometricSourceType: BiometricSourceType) {
+            if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
+                val lockedOut = keyguardUpdateMonitor.isFingerprintLockedOut
+                if (lockedOut && !fingerprintLockedOut) {
+                    uiEventLogger.log(
+                            PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT)
+                } else if (!lockedOut && fingerprintLockedOut) {
+                    uiEventLogger.log(
+                            PrimaryAuthRequiredEvent
+                                    .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET)
+                }
+                fingerprintLockedOut = lockedOut
+            } else if (biometricSourceType == BiometricSourceType.FACE) {
+                val lockedOut = keyguardUpdateMonitor.isFaceLockedOut
+                if (lockedOut && !faceLockedOut) {
+                    uiEventLogger.log(
+                            PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT)
+                } else if (!lockedOut && faceLockedOut) {
+                    uiEventLogger.log(
+                            PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET)
+                }
+                faceLockedOut = lockedOut
+            }
+        }
+
+        override fun onStrongAuthStateChanged(userId: Int) {
+            if (userId != KeyguardUpdateMonitor.getCurrentUser()) {
+                return
+            }
+            val strongAuthFlags = keyguardUpdateMonitor.strongAuthTracker
+                    .getStrongAuthForUser(userId)
+
+            val newEncryptedOrLockdown = keyguardUpdateMonitor.isEncryptedOrLockdown(userId)
+            if (newEncryptedOrLockdown && !encryptedOrLockdown) {
+                uiEventLogger.log(
+                        PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN)
+            }
+            encryptedOrLockdown = newEncryptedOrLockdown
+
+            val newUnattendedUpdate = isUnattendedUpdate(strongAuthFlags)
+            if (newUnattendedUpdate && !unattendedUpdate) {
+                uiEventLogger.log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE)
+            }
+            unattendedUpdate = newUnattendedUpdate
+
+            val newTimeout = isStrongAuthTimeout(strongAuthFlags)
+            if (newTimeout && !timeout) {
+                uiEventLogger.log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_TIMEOUT)
+            }
+            timeout = newTimeout
+        }
+    }
+
+    private fun isUnattendedUpdate(
+        @LockPatternUtils.StrongAuthTracker.StrongAuthFlags flags: Int
+    ) = containsFlag(flags, STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE)
+
+    private fun isStrongAuthTimeout(
+        @LockPatternUtils.StrongAuthTracker.StrongAuthFlags flags: Int
+    ) = containsFlag(flags, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT) ||
+            containsFlag(flags, STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT)
+
+    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
+        pw.println("  mFingerprintLockedOut=$fingerprintLockedOut")
+        pw.println("  mFaceLockedOut=$faceLockedOut")
+        pw.println("  mIsEncryptedOrLockdown=$encryptedOrLockdown")
+        pw.println("  mIsUnattendedUpdate=$unattendedUpdate")
+        pw.println("  mIsTimeout=$timeout")
+    }
+
+    /**
+     * Events pertaining to whether primary authentication (pin/pattern/password input) is required
+     * for device entry.
+     */
+    @VisibleForTesting
+    enum class PrimaryAuthRequiredEvent(private val mId: Int) : UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "Fingerprint cannot be used to authenticate for device entry. This" +
+                "can persist until the next primary auth or may timeout.")
+        PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT(924),
+
+        @UiEvent(doc = "Fingerprint can be used to authenticate for device entry.")
+        PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET(925),
+
+        @UiEvent(doc = "Face cannot be used to authenticate for device entry.")
+        PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT(926),
+
+        @UiEvent(doc = "Face can be used to authenticate for device entry.")
+        PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET(927),
+
+        @UiEvent(doc = "Device is encrypted (ie: after reboot) or device is locked down by DPM " +
+                "or a manual user lockdown.")
+        PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN(928),
+
+        @UiEvent(doc = "Primary authentication is required because it hasn't been used for a " +
+                "time required by a device admin or because primary auth hasn't been used for a " +
+                "time after a non-strong biometric (weak or convenience) is used to unlock the " +
+                "device.")
+        PRIMARY_AUTH_REQUIRED_TIMEOUT(929),
+
+        @UiEvent(doc = "Strong authentication is required to prepare for unattended upgrade.")
+        PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE(931);
+
+        override fun getId(): Int {
+            return mId
+        }
+    }
+
+    companion object {
+        private fun containsFlag(strongAuthFlags: Int, flagCheck: Int): Boolean {
+            return strongAuthFlags and flagCheck != 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index 03f04d3..36fe5ba 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -64,3 +64,19 @@
     val secureCameraLaunched: Boolean,
     val switchingUser: Boolean
 ) : KeyguardListenModel()
+/**
+ * Verbose debug information associated with [KeyguardUpdateMonitor.shouldTriggerActiveUnlock].
+ */
+data class KeyguardActiveUnlockModel(
+    @CurrentTimeMillisLong override val timeMillis: Long,
+    override val userId: Int,
+    override val listening: Boolean,
+    // keep sorted
+    val authInterruptActive: Boolean,
+    val encryptedOrTimedOut: Boolean,
+    val fpLockout: Boolean,
+    val lockDown: Boolean,
+    val switchingUser: Boolean,
+    val triggerActiveUnlockForAssistant: Boolean,
+    val userCanDismissLockScreen: Boolean
+) : KeyguardListenModel()
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt
index f13a59a..210f5e7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt
@@ -32,15 +32,17 @@
 ) {
     private val faceQueue = ArrayDeque<KeyguardFaceListenModel>()
     private val fingerprintQueue = ArrayDeque<KeyguardFingerprintListenModel>()
+    private val activeUnlockQueue = ArrayDeque<KeyguardActiveUnlockModel>()
 
     @get:VisibleForTesting val models: List<KeyguardListenModel>
-        get() = faceQueue + fingerprintQueue
+        get() = faceQueue + fingerprintQueue + activeUnlockQueue
 
     /** Push a [model] to the queue (will be logged until the queue exceeds [sizePerModality]). */
     fun add(model: KeyguardListenModel) {
         val queue = when (model) {
             is KeyguardFaceListenModel -> faceQueue.apply { add(model) }
             is KeyguardFingerprintListenModel -> fingerprintQueue.apply { add(model) }
+            is KeyguardActiveUnlockModel -> activeUnlockQueue.apply { add(model) }
         }
 
         if (queue.size > sizePerModality) {
@@ -63,5 +65,9 @@
         for (model in fingerprintQueue) {
             writer.println(stringify(model))
         }
+        writer.println("  Active unlock triggers (last ${activeUnlockQueue.size} calls):")
+        for (model in activeUnlockQueue) {
+            writer.println(stringify(model))
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
index 099dd5d..75579b0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
@@ -30,6 +30,8 @@
 import android.view.ViewGroup;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.policy.SystemBarUtils;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
@@ -57,6 +59,11 @@
     private ColorStateList mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR);
     private boolean mBouncerVisible;
     private boolean mAltBouncerShowing;
+    /**
+     * Container that wraps the KeyguardMessageArea - may be null if current view hierarchy doesn't
+     * contain {@link R.id.keyguard_message_area_container}.
+     */
+    @Nullable
     private ViewGroup mContainer;
     private int mTopMargin;
 
@@ -75,6 +82,9 @@
     }
 
     void onConfigChanged() {
+        if (mContainer == null) {
+            return;
+        }
         final int newTopMargin = SystemBarUtils.getStatusBarHeight(getContext());
         if (mTopMargin == newTopMargin) {
             return;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 98721fd..5276679 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -37,6 +37,8 @@
 import android.app.ActivityTaskManager;
 import android.app.ActivityTaskManager.RootTaskInfo;
 import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.UserSwitchObserver;
 import android.app.admin.DevicePolicyManager;
@@ -102,6 +104,8 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -109,6 +113,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.util.Assert;
+import com.android.systemui.util.NotificationChannels;
 import com.android.systemui.util.RingerModeTracker;
 
 import com.google.android.collect.Lists;
@@ -143,8 +148,10 @@
     private static final boolean DEBUG_SIM_STATES = KeyguardConstants.DEBUG_SIM_STATES;
     private static final boolean DEBUG_FACE = Build.IS_DEBUGGABLE;
     private static final boolean DEBUG_FINGERPRINT = Build.IS_DEBUGGABLE;
+    private static final boolean DEBUG_ACTIVE_UNLOCK = Build.IS_DEBUGGABLE;
     private static final boolean DEBUG_SPEW = false;
     private static final int BIOMETRIC_LOCKOUT_RESET_DELAY_MS = 600;
+    private int mNumActiveUnlockTriggers = 0;
 
     private static final String ACTION_FACE_UNLOCK_STARTED
             = "com.android.facelock.FACE_UNLOCK_STARTED";
@@ -183,7 +190,6 @@
     private static final int MSG_USER_STOPPED = 340;
     private static final int MSG_USER_REMOVED = 341;
     private static final int MSG_KEYGUARD_GOING_AWAY = 342;
-    private static final int MSG_LOCK_SCREEN_MODE = 343;
     private static final int MSG_TIME_FORMAT_UPDATE = 344;
     private static final int MSG_REQUIRE_NFC_UNLOCK = 345;
 
@@ -221,7 +227,6 @@
     private static final int BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED = -1;
     public static final int BIOMETRIC_HELP_FACE_NOT_RECOGNIZED = -2;
 
-    private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000;
     /**
      * If no cancel signal has been received after this amount of time, set the biometric running
      * state to stopped to allow Keyguard to retry authentication.
@@ -231,7 +236,6 @@
     private static final ComponentName FALLBACK_HOME_COMPONENT = new ComponentName(
             "com.android.settings", "com.android.settings.FallbackHome");
 
-
     /**
      * If true, the system is in the half-boot-to-decryption-screen state.
      * Prudently disable lockscreen.
@@ -334,6 +338,7 @@
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private final Executor mBackgroundExecutor;
     private SensorPrivacyManager mSensorPrivacyManager;
+    private FeatureFlags mFeatureFlags;
     private int mFaceAuthUserId;
 
     /**
@@ -1250,7 +1255,11 @@
                 STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
     }
 
-    private boolean isEncryptedOrLockdown(int userId) {
+    /**
+     * Returns true if primary authentication is required for the given user due to lockdown
+     * or encryption after reboot.
+     */
+    public boolean isEncryptedOrLockdown(int userId) {
         final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(userId);
         final boolean isLockDown =
                 containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW)
@@ -1311,6 +1320,9 @@
     void setAssistantVisible(boolean assistantVisible) {
         mAssistantVisible = assistantVisible;
         updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+        if (mAssistantVisible) {
+            requestActiveUnlock();
+        }
     }
 
     static class DisplayClientState {
@@ -1650,6 +1662,7 @@
         Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp");
         Assert.isMainThread();
         updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+        requestActiveUnlock();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -1777,7 +1790,8 @@
             AuthController authController,
             TelephonyListenerManager telephonyListenerManager,
             InteractionJankMonitor interactionJankMonitor,
-            LatencyTracker latencyTracker) {
+            LatencyTracker latencyTracker,
+            FeatureFlags featureFlags) {
         mContext = context;
         mSubscriptionManager = SubscriptionManager.from(context);
         mTelephonyListenerManager = telephonyListenerManager;
@@ -1795,6 +1809,7 @@
         mAuthController = authController;
         dumpManager.registerDumpable(getClass().getName(), this);
         mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+        mFeatureFlags = featureFlags;
 
         mHandler = new Handler(mainLooper) {
             @Override
@@ -2180,6 +2195,7 @@
         }
         mAuthInterruptActive = active;
         updateFaceListeningState(BIOMETRIC_ACTION_UPDATE);
+        requestActiveUnlock();
     }
 
     /**
@@ -2228,6 +2244,97 @@
         }
     }
 
+    /**
+     * Attempts to trigger active unlock.
+     */
+    public void requestActiveUnlock() {
+        // If this message exists, FP has already authenticated, so wait until that is handled
+        if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) {
+            return;
+        }
+
+        if (shouldTriggerActiveUnlock() && mFeatureFlags.isEnabled(Flags.ACTIVE_UNLOCK)) {
+            // TODO (b/192405661): call new TrustManager API
+            mNumActiveUnlockTriggers++;
+            Log.d("ActiveUnlock", "would have triggered times=" + mNumActiveUnlockTriggers);
+            showActiveUnlockNotification(mNumActiveUnlockTriggers);
+        }
+    }
+
+    /**
+     * TODO (b/192405661): Only for testing. Remove before release.
+     */
+    private void showActiveUnlockNotification(int times) {
+        final String message = "Active unlock triggered "  + times + " times.";
+        final Notification.Builder nb =
+                new Notification.Builder(mContext, NotificationChannels.GENERAL)
+                        .setSmallIcon(R.drawable.ic_volume_ringer)
+                        .setContentTitle(message)
+                        .setStyle(new Notification.BigTextStyle().bigText(message));
+        mContext.getSystemService(NotificationManager.class).notifyAsUser(
+                "active_unlock",
+                0,
+                nb.build(),
+                UserHandle.ALL);
+    }
+
+    private boolean shouldTriggerActiveUnlock() {
+        // TODO: check if active unlock is ENABLED / AVAILABLE
+
+        // Triggers:
+        final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant();
+        final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep
+                && mStatusBarState != StatusBarState.SHADE_LOCKED;
+
+        // Gates:
+        final int user = getCurrentUser();
+
+        // No need to trigger active unlock if we're already unlocked or don't have
+        // pin/pattern/password setup
+        final boolean userCanDismissLockScreen = getUserCanSkipBouncer(user)
+                || !mLockPatternUtils.isSecure(user);
+
+        // Don't trigger active unlock if fp is locked out TODO: confirm this one
+        final boolean fpLockedout = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
+
+        // Don't trigger active unlock if primary auth is required
+        final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
+        final boolean isLockDown =
+                containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW)
+                        || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+        final boolean isEncryptedOrTimedOut =
+                containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT)
+                        || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
+
+        final boolean shouldTriggerActiveUnlock =
+                (mAuthInterruptActive || triggerActiveUnlockForAssistant || awakeKeyguard)
+                        && !mSwitchingUser
+                        && !userCanDismissLockScreen
+                        && !fpLockedout
+                        && !isLockDown
+                        && !isEncryptedOrTimedOut
+                        && !mKeyguardGoingAway
+                        && !mSecureCameraLaunched;
+
+        // Aggregate relevant fields for debug logging.
+        if (DEBUG_ACTIVE_UNLOCK || DEBUG_SPEW) {
+            maybeLogListenerModelData(
+                    new KeyguardActiveUnlockModel(
+                            System.currentTimeMillis(),
+                            user,
+                            shouldTriggerActiveUnlock,
+                            mAuthInterruptActive,
+                            isEncryptedOrTimedOut,
+                            fpLockedout,
+                            isLockDown,
+                            mSwitchingUser,
+                            triggerActiveUnlockForAssistant,
+                            userCanDismissLockScreen));
+        }
+
+        return shouldTriggerActiveUnlock;
+    }
+
     private boolean shouldListenForFingerprintAssistant() {
         BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(getCurrentUser());
         return mAssistantVisible && mKeyguardOccluded
@@ -2242,6 +2349,11 @@
                 && !mUserHasTrust.get(getCurrentUser(), false);
     }
 
+    private boolean shouldTriggerActiveUnlockForAssistant() {
+        return mAssistantVisible && mKeyguardOccluded
+                && !mUserHasTrust.get(getCurrentUser(), false);
+    }
+
     @VisibleForTesting
     protected boolean shouldListenForFingerprint(boolean isUdfps) {
         final int user = getCurrentUser();
@@ -2406,6 +2518,13 @@
             Log.v(TAG, model.toString());
         }
 
+        if (DEBUG_ACTIVE_UNLOCK
+                && model instanceof KeyguardActiveUnlockModel
+                && model.getListening()) {
+            mListenModels.add(model);
+            return;
+        }
+
         // Add model data to the historical buffer.
         final boolean notYetRunning =
                 (DEBUG_FACE
@@ -2514,6 +2633,10 @@
         return mFingerprintLockedOut || mFingerprintLockedOutPermanent;
     }
 
+    public boolean isFaceLockedOut() {
+        return mFaceLockedOutPermanent;
+    }
+
     /**
      * If biometrics hardware is available, not disabled, and user has enrolled templates.
      * This does NOT check if the device is encrypted or in lockdown.
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 33538ec..a100cb8 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -342,7 +342,7 @@
             mDisplayManager.getDisplay(DEFAULT_DISPLAY).getMetrics(metrics);
             mDensity = metrics.density;
 
-            mExecutor.execute(() -> mTunerService.addTunable(this, SIZE));
+            mMainExecutor.execute(() -> mTunerService.addTunable(this, SIZE));
 
             // Watch color inversion and invert the overlay as needed.
             if (mColorInversionSetting == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
index 7bb4708..4c00735 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
@@ -26,6 +26,7 @@
 import android.hardware.biometrics.BiometricOverlayConstants
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
+import android.hardware.biometrics.SensorLocationInternal
 import android.hardware.display.DisplayManager
 import android.hardware.fingerprint.FingerprintManager
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
@@ -113,6 +114,7 @@
                 orientationListener.enable()
             }
         }
+    private var overlayOffsets: SensorLocationInternal = SensorLocationInternal.DEFAULT
 
     private val overlayViewParams = WindowManager.LayoutParams(
         WindowManager.LayoutParams.WRAP_CONTENT,
@@ -158,11 +160,19 @@
         val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
         val display = context.display!!
 
+        val offsets = sensorProps.getLocation(display.uniqueId).let { location ->
+            if (location == null) {
+                Log.w(TAG, "No location specified for display: ${display.uniqueId}")
+            }
+            location ?: sensorProps.location
+        }
+        overlayOffsets = offsets
+
         val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
-        lottie.setAnimation(display.asSideFpsAnimation())
-        view.rotation = display.asSideFpsAnimationRotation()
+        view.rotation = display.asSideFpsAnimationRotation(offsets.isYAligned())
 
         updateOverlayParams(display, lottie.composition?.bounds ?: Rect())
+        lottie.setAnimation(display.asSideFpsAnimation(offsets.isYAligned()))
         lottie.addLottieOnCompositionLoadedListener {
             if (overlayView == view) {
                 updateOverlayParams(display, it.bounds)
@@ -179,24 +189,37 @@
         val size = windowManager.maximumWindowMetrics.bounds
         val displayWidth = if (isPortrait) size.width() else size.height()
         val displayHeight = if (isPortrait) size.height() else size.width()
-        val offsets = sensorProps.getLocation(display.uniqueId).let { location ->
-            if (location == null) {
-                Log.w(TAG, "No location specified for display: ${display.uniqueId}")
-            }
-            location ?: sensorProps.location
-        }
 
-        // ignore sensorLocationX and sensorRadius since it's assumed to be on the side
-        // of the device and centered at sensorLocationY
-        val (x, y) = when (display.rotation) {
-            Surface.ROTATION_90 ->
-                Pair(offsets.sensorLocationY, 0)
-            Surface.ROTATION_270 ->
-                Pair(displayHeight - offsets.sensorLocationY - bounds.width(), displayWidth)
-            Surface.ROTATION_180 ->
-                Pair(0, displayHeight - offsets.sensorLocationY - bounds.height())
-            else ->
-                Pair(displayWidth, offsets.sensorLocationY)
+        // ignore sensorRadius since it's assumed that the sensor is on the side and centered at
+        // either sensorLocationX or sensorLocationY (both should not be set)
+        val (x, y) = if (overlayOffsets.isYAligned()) {
+            when (display.rotation) {
+                Surface.ROTATION_90 ->
+                    Pair(overlayOffsets.sensorLocationY, 0)
+                Surface.ROTATION_270 ->
+                    Pair(
+                        displayHeight - overlayOffsets.sensorLocationY - bounds.width(),
+                        displayWidth + bounds.height()
+                    )
+                Surface.ROTATION_180 ->
+                    Pair(0, displayHeight - overlayOffsets.sensorLocationY - bounds.height())
+                else ->
+                    Pair(displayWidth, overlayOffsets.sensorLocationY)
+            }
+        } else {
+            when (display.rotation) {
+                Surface.ROTATION_90 ->
+                    Pair(0, displayWidth - overlayOffsets.sensorLocationX - bounds.height())
+                Surface.ROTATION_270 ->
+                    Pair(displayWidth, overlayOffsets.sensorLocationX - bounds.height())
+                Surface.ROTATION_180 ->
+                    Pair(
+                        displayWidth - overlayOffsets.sensorLocationX - bounds.width(),
+                        displayHeight
+                    )
+                else ->
+                    Pair(overlayOffsets.sensorLocationX, 0)
+            }
         }
         overlayViewParams.x = x
         overlayViewParams.y = y
@@ -209,8 +232,10 @@
 
         // hide after a few seconds if the sensor is oriented down and there are
         // large overlapping system bars
-        if ((context.display?.rotation == Surface.ROTATION_270) &&
-            windowManager.currentWindowMetrics.windowInsets.hasBigNavigationBar()) {
+        val rotation = context.display?.rotation
+        if (windowManager.currentWindowMetrics.windowInsets.hasBigNavigationBar() &&
+            ((rotation == Surface.ROTATION_270 && overlayOffsets.isYAligned()) ||
+                    (rotation == Surface.ROTATION_180 && !overlayOffsets.isYAligned()))) {
             overlayHideAnimator = view.animate()
                 .alpha(0f)
                 .setStartDelay(3_000)
@@ -245,18 +270,21 @@
     getTasks(1).firstOrNull()?.topActivity?.className ?: ""
 
 @RawRes
-private fun Display.asSideFpsAnimation(): Int = when (rotation) {
-    Surface.ROTATION_0 -> R.raw.sfps_pulse
-    Surface.ROTATION_180 -> R.raw.sfps_pulse
-    else -> R.raw.sfps_pulse_landscape
+private fun Display.asSideFpsAnimation(yAligned: Boolean): Int = when (rotation) {
+    Surface.ROTATION_0 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
+    Surface.ROTATION_180 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
+    else -> if (yAligned) R.raw.sfps_pulse_landscape else R.raw.sfps_pulse
 }
 
-private fun Display.asSideFpsAnimationRotation(): Float = when (rotation) {
+private fun Display.asSideFpsAnimationRotation(yAligned: Boolean): Float = when (rotation) {
+    Surface.ROTATION_90 -> if (yAligned) 0f else 180f
     Surface.ROTATION_180 -> 180f
-    Surface.ROTATION_270 -> 180f
+    Surface.ROTATION_270 -> if (yAligned) 180f else 0f
     else -> 0f
 }
 
+private fun SensorLocationInternal.isYAligned(): Boolean = sensorLocationY != 0
+
 private fun Display.isPortrait(): Boolean =
     rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
 
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
new file mode 100644
index 0000000..41a4963
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.clipboardoverlay;
+
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED;
+
+import static java.util.Objects.requireNonNull;
+
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.provider.DeviceConfig;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.screenshot.TimeoutHandler;
+
+import javax.inject.Inject;
+
+/**
+ * ClipboardListener brings up a clipboard overlay when something is copied to the clipboard.
+ */
+@SysUISingleton
+public class ClipboardListener extends CoreStartable
+        implements ClipboardManager.OnPrimaryClipChangedListener {
+
+    private ClipboardOverlayController mClipboardOverlayController;
+    private ClipboardManager mClipboardManager;
+
+    @Inject
+    public ClipboardListener(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void start() {
+        if (DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, false)) {
+            mClipboardManager = requireNonNull(mContext.getSystemService(ClipboardManager.class));
+            mClipboardManager.addPrimaryClipChangedListener(this);
+        }
+    }
+
+    @Override
+    public void onPrimaryClipChanged() {
+        if (!mClipboardManager.hasPrimaryClip()) {
+            return;
+        }
+        if (mClipboardOverlayController == null) {
+            mClipboardOverlayController = new ClipboardOverlayController(mContext,
+                    new TimeoutHandler(mContext));
+        }
+        mClipboardOverlayController.setClipData(mClipboardManager.getPrimaryClip());
+        mClipboardOverlayController.setOnSessionCompleteListener(() -> {
+            // Session is complete, free memory until it's needed again.
+            mClipboardOverlayController = null;
+        });
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
new file mode 100644
index 0000000..ae0702c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.clipboardoverlay;
+
+import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
+
+import static java.util.Objects.requireNonNull;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.MainThread;
+import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.graphics.drawable.Icon;
+import android.hardware.display.DisplayManager;
+import android.hardware.input.InputManager;
+import android.net.Uri;
+import android.os.Looper;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Size;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.Gravity;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.InputMonitor;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.policy.PhoneWindow;
+import com.android.systemui.R;
+import com.android.systemui.screenshot.FloatingWindowUtil;
+import com.android.systemui.screenshot.ScreenshotActionChip;
+import com.android.systemui.screenshot.TimeoutHandler;
+
+import java.io.IOException;
+
+/**
+ * Controls state and UI for the overlay that appears when something is added to the clipboard
+ */
+public class ClipboardOverlayController {
+    private static final String TAG = "ClipboardOverlayCtrlr";
+    private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY";
+
+    /** Constants for screenshot/copy deconflicting */
+    public static final String SCREENSHOT_ACTION = "com.android.systemui.SCREENSHOT";
+    public static final String SELF_PERMISSION = "com.android.systemui.permission.SELF";
+    public static final String COPY_OVERLAY_ACTION = "com.android.systemui.COPY";
+
+    private static final int CLIPBOARD_DEFAULT_TIMEOUT_MILLIS = 6000;
+
+    private final Context mContext;
+    private final DisplayManager mDisplayManager;
+    private final WindowManager mWindowManager;
+    private final WindowManager.LayoutParams mWindowLayoutParams;
+    private final PhoneWindow mWindow;
+    private final TimeoutHandler mTimeoutHandler;
+
+    private final DraggableConstraintLayout mView;
+    private final ImageView mImagePreview;
+    private final TextView mTextPreview;
+    private final ScreenshotActionChip mEditChip;
+    private final ScreenshotActionChip mRemoteCopyChip;
+    private final View mActionContainerBackground;
+
+    private Runnable mOnSessionCompleteListener;
+
+    private InputEventReceiver mInputEventReceiver;
+
+    private BroadcastReceiver mCloseDialogsReceiver;
+    private BroadcastReceiver mScreenshotReceiver;
+
+    private boolean mBlockAttach = false;
+
+    public ClipboardOverlayController(Context context, TimeoutHandler timeoutHandler) {
+        mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
+        final Context displayContext = context.createDisplayContext(getDefaultDisplay());
+        mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null);
+
+        mWindowManager = mContext.getSystemService(WindowManager.class);
+
+        mTimeoutHandler = timeoutHandler;
+        mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS);
+
+        // Setup the window that we are going to use
+        mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
+        mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        mWindowLayoutParams.height = WRAP_CONTENT;
+        mWindowLayoutParams.gravity = Gravity.BOTTOM;
+        mWindowLayoutParams.setTitle("ClipboardOverlay");
+        mWindow = FloatingWindowUtil.getFloatingWindow(mContext);
+        mWindow.setWindowManager(mWindowManager, null, null);
+
+        mView = (DraggableConstraintLayout)
+                LayoutInflater.from(mContext).inflate(R.layout.clipboard_overlay, null);
+        mActionContainerBackground = requireNonNull(
+                mView.findViewById(R.id.actions_container_background));
+        mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview));
+        mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
+        mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip));
+        mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip));
+
+        mView.setOnDismissCallback(this::hideImmediate);
+        mView.setOnInteractionCallback(() -> mTimeoutHandler.resetTimeout());
+
+        mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true);
+        mRemoteCopyChip.setIcon(
+                Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true);
+
+        // Only show remote copy if it's available.
+        PackageManager packageManager = mContext.getPackageManager();
+        if (packageManager.resolveActivity(getRemoteCopyIntent(), 0) != null) {
+            mRemoteCopyChip.setOnClickListener((v) -> {
+                showNearby();
+            });
+            mRemoteCopyChip.setAlpha(1f);
+        } else {
+            mRemoteCopyChip.setVisibility(View.GONE);
+        }
+
+        attachWindow();
+        withWindowAttached(() -> {
+            mWindow.setContentView(mView);
+            updateInsets(mWindowManager.getCurrentWindowMetrics().getWindowInsets());
+            getEnterAnimation().start();
+        });
+
+        mTimeoutHandler.setOnTimeoutRunnable(() -> animateOut());
+
+        mCloseDialogsReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
+                    animateOut();
+                }
+            }
+        };
+        mContext.registerReceiver(mCloseDialogsReceiver,
+                new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS));
+
+        mScreenshotReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (SCREENSHOT_ACTION.equals(intent.getAction())) {
+                    animateOut();
+                }
+            }
+        };
+        mContext.registerReceiver(mScreenshotReceiver, new IntentFilter(SCREENSHOT_ACTION),
+                SELF_PERMISSION, null);
+        monitorOutsideTouches();
+
+        mContext.sendBroadcast(new Intent(COPY_OVERLAY_ACTION), SELF_PERMISSION);
+    }
+
+    void setClipData(ClipData clipData) {
+        reset();
+
+        if (clipData == null || clipData.getItemCount() == 0) {
+            showTextPreview(
+                    mContext.getResources().getString(R.string.clipboard_overlay_text_copied));
+        } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
+            showEditableText(clipData.getItemAt(0).getText());
+        } else if (clipData.getItemAt(0).getUri() != null) {
+            // How to handle non-image URIs?
+            showEditableImage(clipData.getItemAt(0).getUri());
+        } else {
+            showTextPreview(
+                    mContext.getResources().getString(R.string.clipboard_overlay_text_copied));
+        }
+
+        mTimeoutHandler.resetTimeout();
+    }
+
+    void setOnSessionCompleteListener(Runnable runnable) {
+        mOnSessionCompleteListener = runnable;
+    }
+
+    private void monitorOutsideTouches() {
+        InputManager inputManager = mContext.getSystemService(InputManager.class);
+        InputMonitor monitor = inputManager.monitorGestureInput("clipboard overlay", 0);
+        mInputEventReceiver = new InputEventReceiver(monitor.getInputChannel(),
+                Looper.getMainLooper()) {
+            @Override
+            public void onInputEvent(InputEvent event) {
+                if (event instanceof MotionEvent) {
+                    MotionEvent motionEvent = (MotionEvent) event;
+                    if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
+                        int[] pt = new int[2];
+                        mView.getLocationOnScreen(pt);
+                        Rect rect = new Rect(pt[0], pt[1], pt[0] + mView.getWidth(),
+                                pt[1] + mView.getHeight());
+                        if (!rect.contains((int) motionEvent.getRawX(),
+                                (int) motionEvent.getRawY())) {
+                            animateOut();
+                        }
+                    }
+                }
+                finishInputEvent(event, true /* handled */);
+            }
+        };
+    }
+
+    private void editImage(Uri uri) {
+        String editorPackage = mContext.getString(R.string.config_screenshotEditor);
+        Intent editIntent = new Intent(Intent.ACTION_EDIT);
+        if (!TextUtils.isEmpty(editorPackage)) {
+            editIntent.setComponent(ComponentName.unflattenFromString(editorPackage));
+        }
+        editIntent.setDataAndType(uri, "image/*");
+        editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        mContext.startActivity(editIntent);
+        animateOut();
+    }
+
+    private void editText() {
+        Intent editIntent = new Intent(mContext, EditTextActivity.class);
+        editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        mContext.startActivity(editIntent);
+        animateOut();
+    }
+
+    private void showNearby() {
+        mContext.startActivity(getRemoteCopyIntent());
+        animateOut();
+    }
+
+    private void showTextPreview(CharSequence text) {
+        mTextPreview.setVisibility(View.VISIBLE);
+        mImagePreview.setVisibility(View.GONE);
+        mTextPreview.setText(text);
+        mEditChip.setVisibility(View.GONE);
+    }
+
+    private void showEditableText(CharSequence text) {
+        showTextPreview(text);
+        mEditChip.setVisibility(View.VISIBLE);
+        mEditChip.setAlpha(1f);
+        View.OnClickListener listener = v -> editText();
+        mEditChip.setOnClickListener(listener);
+        mTextPreview.setOnClickListener(listener);
+    }
+
+    private void showEditableImage(Uri uri) {
+        mTextPreview.setVisibility(View.GONE);
+        mImagePreview.setVisibility(View.VISIBLE);
+        mEditChip.setAlpha(1f);
+        ContentResolver resolver = mContext.getContentResolver();
+        try {
+            int size = mContext.getResources().getDimensionPixelSize(R.dimen.screenshot_x_scale);
+            // The width of the view is capped, height maintains aspect ratio, so allow it to be
+            // taller if needed.
+            Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
+            mImagePreview.setImageBitmap(thumbnail);
+        } catch (IOException e) {
+            Log.e(TAG, "Thumbnail loading failed", e);
+        }
+        View.OnClickListener listener = v -> editImage(uri);
+        mEditChip.setOnClickListener(listener);
+        mImagePreview.setOnClickListener(listener);
+    }
+
+    private Intent getRemoteCopyIntent() {
+        Intent nearbyIntent = new Intent(REMOTE_COPY_ACTION);
+        nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        return nearbyIntent;
+    }
+
+    private void animateOut() {
+        getExitAnimation().start();
+    }
+
+    private ValueAnimator getEnterAnimation() {
+        ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+
+        mView.setAlpha(0);
+        final View previewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
+        final View actionBackground = requireNonNull(
+                mView.findViewById(R.id.actions_container_background));
+        mImagePreview.setVisibility(View.VISIBLE);
+        mActionContainerBackground.setVisibility(View.VISIBLE);
+
+        anim.addUpdateListener(animation -> {
+            mView.setAlpha(animation.getAnimatedFraction());
+            float scale = 0.6f + 0.4f * animation.getAnimatedFraction();
+            mView.setPivotY(mView.getHeight() - previewBorder.getHeight() / 2f);
+            mView.setPivotX(actionBackground.getWidth() / 2f);
+            mView.setScaleX(scale);
+            mView.setScaleY(scale);
+        });
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                mView.setAlpha(1);
+                mTimeoutHandler.resetTimeout();
+            }
+        });
+        return anim;
+    }
+
+    private ValueAnimator getExitAnimation() {
+        ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+
+        anim.addUpdateListener(animation -> {
+            mView.setAlpha(1 - animation.getAnimatedFraction());
+            final View actionBackground = requireNonNull(
+                    mView.findViewById(R.id.actions_container_background));
+            mView.setTranslationX(
+                    -animation.getAnimatedFraction() * actionBackground.getWidth() / 2);
+        });
+
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                hideImmediate();
+            }
+        });
+
+        return anim;
+    }
+
+    private void hideImmediate() {
+        // Note this may be called multiple times if multiple dismissal events happen at the same
+        // time.
+        mTimeoutHandler.cancelTimeout();
+        final View decorView = mWindow.peekDecorView();
+        if (decorView != null && decorView.isAttachedToWindow()) {
+            mWindowManager.removeViewImmediate(decorView);
+        }
+        if (mCloseDialogsReceiver != null) {
+            mContext.unregisterReceiver(mCloseDialogsReceiver);
+            mCloseDialogsReceiver = null;
+        }
+        if (mScreenshotReceiver != null) {
+            mContext.unregisterReceiver(mScreenshotReceiver);
+            mScreenshotReceiver = null;
+        }
+        if (mInputEventReceiver != null) {
+            mInputEventReceiver.dispose();
+            mInputEventReceiver = null;
+        }
+        if (mOnSessionCompleteListener != null) {
+            mOnSessionCompleteListener.run();
+        }
+    }
+
+    private void reset() {
+        mView.setTranslationX(0);
+        mView.setAlpha(1);
+        mTimeoutHandler.cancelTimeout();
+    }
+
+    @MainThread
+    private void attachWindow() {
+        View decorView = mWindow.getDecorView();
+        if (decorView.isAttachedToWindow() || mBlockAttach) {
+            return;
+        }
+        mBlockAttach = true;
+        mWindowManager.addView(decorView, mWindowLayoutParams);
+        decorView.requestApplyInsets();
+        mView.requestApplyInsets();
+        decorView.getViewTreeObserver().addOnWindowAttachListener(
+                new ViewTreeObserver.OnWindowAttachListener() {
+                    @Override
+                    public void onWindowAttached() {
+                        mBlockAttach = false;
+                    }
+
+                    @Override
+                    public void onWindowDetached() {
+                    }
+                }
+        );
+    }
+
+    private void withWindowAttached(Runnable action) {
+        View decorView = mWindow.getDecorView();
+        if (decorView.isAttachedToWindow()) {
+            action.run();
+        } else {
+            decorView.getViewTreeObserver().addOnWindowAttachListener(
+                    new ViewTreeObserver.OnWindowAttachListener() {
+                        @Override
+                        public void onWindowAttached() {
+                            mBlockAttach = false;
+                            decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
+                            action.run();
+                        }
+
+                        @Override
+                        public void onWindowDetached() {
+                        }
+                    });
+        }
+    }
+
+    private void updateInsets(WindowInsets insets) {
+        int orientation = mContext.getResources().getConfiguration().orientation;
+        FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) mView.getLayoutParams();
+        if (p == null) {
+            return;
+        }
+        DisplayCutout cutout = insets.getDisplayCutout();
+        Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
+        if (cutout == null) {
+            p.setMargins(0, 0, 0, navBarInsets.bottom);
+        } else {
+            Insets waterfall = cutout.getWaterfallInsets();
+            if (orientation == ORIENTATION_PORTRAIT) {
+                p.setMargins(
+                        waterfall.left,
+                        Math.max(cutout.getSafeInsetTop(), waterfall.top),
+                        waterfall.right,
+                        Math.max(cutout.getSafeInsetBottom(),
+                                Math.max(navBarInsets.bottom, waterfall.bottom)));
+            } else {
+                p.setMargins(
+                        Math.max(cutout.getSafeInsetLeft(), waterfall.left),
+                        waterfall.top,
+                        Math.max(cutout.getSafeInsetRight(), waterfall.right),
+                        Math.max(navBarInsets.bottom, waterfall.bottom));
+            }
+        }
+        mView.setLayoutParams(p);
+        mView.requestLayout();
+    }
+
+    private Display getDefaultDisplay() {
+        return mDisplayManager.getDisplay(DEFAULT_DISPLAY);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java
new file mode 100644
index 0000000..6a4be6e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.clipboardoverlay;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+import com.android.systemui.R;
+import com.android.systemui.screenshot.SwipeDismissHandler;
+
+/**
+ * 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 Runnable mOnInteraction;
+
+    public DraggableConstraintLayout(Context context) {
+        this(context, null);
+    }
+
+    public DraggableConstraintLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public DraggableConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        mSwipeDismissHandler = new SwipeDismissHandler(mContext, this,
+                new SwipeDismissHandler.SwipeDismissCallbacks() {
+                    @Override
+                    public void onInteraction() {
+                        if (mOnInteraction != null) {
+                            mOnInteraction.run();
+                        }
+                    }
+
+                    @Override
+                    public void onDismiss() {
+                        if (mOnDismiss != null) {
+                            mOnDismiss.run();
+                        }
+                    }
+                });
+        setOnTouchListener(mSwipeDismissHandler);
+
+        mSwipeDetector = new GestureDetector(mContext,
+                new GestureDetector.SimpleOnGestureListener() {
+                    final Rect mActionsRect = new Rect();
+
+                    @Override
+                    public boolean onScroll(
+                            MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) {
+                        View actionsContainer = findViewById(R.id.actions_container);
+                        actionsContainer.getBoundsOnScreen(mActionsRect);
+                        // return true if we aren't in the actions bar, or if we are but it isn't
+                        // scrollable in the direction of movement
+                        return !mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY())
+                                || !actionsContainer.canScrollHorizontally((int) distanceX);
+                    }
+                });
+        mSwipeDetector.setIsLongpressEnabled(false);
+    }
+
+    @Override // View
+    protected void onFinishInflate() {
+
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+            mSwipeDismissHandler.onTouch(this, ev);
+        }
+
+        return mSwipeDetector.onTouchEvent(ev);
+    }
+
+    public void setOnDismissCallback(Runnable callback) {
+        mOnDismiss = callback;
+    }
+
+    public void setOnInteractionCallback(Runnable callback) {
+        mOnInteraction = callback;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
new file mode 100644
index 0000000..be10c35
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.clipboardoverlay;
+
+import static java.util.Objects.requireNonNull;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+
+import com.android.systemui.R;
+
+/**
+ * Lightweight activity for editing text clipboard contents
+ */
+public class EditTextActivity extends Activity {
+    private EditText mEditText;
+    private ClipboardManager mClipboardManager;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.clipboard_edit_text_activity);
+        findViewById(R.id.copy_button).setOnClickListener((v) -> saveToClipboard());
+        findViewById(R.id.share).setOnClickListener((v) -> share());
+        mEditText = findViewById(R.id.edit_text);
+        mClipboardManager = requireNonNull(getSystemService(ClipboardManager.class));
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        ClipData clip = mClipboardManager.getPrimaryClip();
+        if (clip == null) {
+            finish();
+            return;
+        }
+        // TODO: put clip attribution in R.id.attribution TextView
+        mEditText.setText(clip.getItemAt(0).getText());
+        mEditText.requestFocus();
+    }
+
+    private void saveToClipboard() {
+        ClipData clip = ClipData.newPlainText("text", mEditText.getText());
+        mClipboardManager.setPrimaryClip(clip);
+        hideImeAndFinish();
+    }
+
+    private void share() {
+        Intent sendIntent = new Intent();
+        sendIntent.setAction(Intent.ACTION_SEND);
+        sendIntent.putExtra(Intent.EXTRA_TEXT, mEditText.getText());
+        sendIntent.setType("text/plain");
+
+        Intent shareIntent = Intent.createChooser(sendIntent, null);
+        startActivity(shareIntent);
+    }
+
+    private void hideImeAndFinish() {
+        InputMethodManager imm = getSystemService(InputMethodManager.class);
+        imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
+        finish();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
index 977e46a..d2ded71 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
@@ -131,6 +131,12 @@
             wrapper = null
             bindService(false)
         }
+
+        override fun onNullBinding(name: ComponentName?) {
+            if (DEBUG) Log.d(TAG, "onNullBinding $name")
+            wrapper = null
+            context.unbindService(this)
+        }
     }
 
     private fun handlePendingServiceMethods() {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index cffb2f7..b235692 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -28,6 +28,8 @@
 import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
 import com.android.systemui.people.PeopleProvider;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.unfold.FoldStateLogger;
+import com.android.systemui.unfold.FoldStateLoggingProvider;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 import com.android.systemui.unfold.UnfoldLatencyTracker;
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
@@ -139,6 +141,8 @@
         getMediaTttChipControllerReceiver();
         getMediaTttCommandLineHelper();
         getUnfoldLatencyTracker().init();
+        getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
+        getFoldStateLogger().ifPresent(FoldStateLogger::init);
     }
 
     /**
@@ -166,6 +170,18 @@
     UnfoldLatencyTracker getUnfoldLatencyTracker();
 
     /**
+     * Creates a FoldStateLoggingProvider.
+     */
+    @SysUISingleton
+    Optional<FoldStateLoggingProvider> getFoldStateLoggingProvider();
+
+    /**
+     * Creates a FoldStateLogger.
+     */
+    @SysUISingleton
+    Optional<FoldStateLogger> getFoldStateLogger();
+
+    /**
      * Main dependency providing module.
      */
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 9dddbb1..00491da 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.dagger;
 
+import com.android.keyguard.KeyguardBiometricLockoutLogger;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.LatencyTester;
 import com.android.systemui.ScreenDecorations;
@@ -23,6 +24,7 @@
 import com.android.systemui.accessibility.SystemActions;
 import com.android.systemui.accessibility.WindowMagnification;
 import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.clipboardoverlay.ClipboardListener;
 import com.android.systemui.communal.CommunalManagerUpdater;
 import com.android.systemui.dreams.DreamOverlayRegistrant;
 import com.android.systemui.dreams.appwidgets.ComplicationPrimer;
@@ -72,6 +74,12 @@
     @ClassKey(GarbageMonitor.Service.class)
     public abstract CoreStartable bindGarbageMonitorService(GarbageMonitor.Service sysui);
 
+    /** Inject into ClipboardListener. */
+    @Binds
+    @IntoMap
+    @ClassKey(ClipboardListener.class)
+    public abstract CoreStartable bindClipboardListener(ClipboardListener sysui);
+
     /** Inject into GlobalActionsComponent. */
     @Binds
     @IntoMap
@@ -90,6 +98,13 @@
     @ClassKey(KeyguardViewMediator.class)
     public abstract CoreStartable bindKeyguardViewMediator(KeyguardViewMediator sysui);
 
+    /** Inject into KeyguardBiometricLockoutLogger. */
+    @Binds
+    @IntoMap
+    @ClassKey(KeyguardBiometricLockoutLogger.class)
+    public abstract CoreStartable bindKeyguardBiometricLockoutLogger(
+            KeyguardBiometricLockoutLogger sysui);
+
     /** Inject into LatencyTests. */
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 4be819a..5d6c2a2 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -74,6 +74,9 @@
     public static final ResourceBooleanFlag BOUNCER_USER_SWITCHER =
             new ResourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher);
 
+    public static final ResourceBooleanFlag ACTIVE_UNLOCK =
+            new ResourceBooleanFlag(205, R.bool.flag_active_unlock);
+
     /***************************************/
     // 300 - power menu
     public static final BooleanFlag POWER_MENU_LITE =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 4658a74..5ca2539 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -100,6 +100,7 @@
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.keyguard.mediator.ScreenOnCoordinator;
 import com.android.systemui.CoreStartable;
+import com.android.systemui.DejankUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -1687,6 +1688,21 @@
                 || mUpdateMonitor.isSimPinSecure();
     }
 
+    /**
+     * Whether any of the SIMs on the device are secured with a PIN. If so, the keyguard should not
+     * be dismissable until the PIN is entered, even if the device itself has no lock set.
+     */
+    public boolean isAnySimPinSecure() {
+        for (int i = 0; i < mLastSimStates.size(); i++) {
+            final int key = mLastSimStates.keyAt(i);
+            if (KeyguardUpdateMonitor.isSimPinSecure(mLastSimStates.get(key))) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
     public void setSwitchingUser(boolean switching) {
         mUpdateMonitor.setSwitchingUser(switching);
     }
@@ -2345,16 +2361,20 @@
         // Block the panel from expanding, in case we were doing a swipe to dismiss gesture.
         mKeyguardViewControllerLazy.get().blockPanelExpansionFromCurrentTouch();
         final boolean wasShowing = mShowing;
-        onKeyguardExitFinished();
-
-        if (mKeyguardStateController.isDismissingFromSwipe() || wasShowing) {
-            mKeyguardUnlockAnimationControllerLazy.get().hideKeyguardViewAfterRemoteAnimation();
-        }
-
-        finishSurfaceBehindRemoteAnimation(cancelled);
-        mSurfaceBehindRemoteAnimationRequested = false;
-        mKeyguardUnlockAnimationControllerLazy.get().notifyFinishedKeyguardExitAnimation();
         InteractionJankMonitor.getInstance().end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
+
+        // Post layout changes to the next frame, so we don't hang at the end of the animation.
+        DejankUtils.postAfterTraversal(() -> {
+            onKeyguardExitFinished();
+
+            if (mKeyguardStateController.isDismissingFromSwipe() || wasShowing) {
+                mKeyguardUnlockAnimationControllerLazy.get().hideKeyguardViewAfterRemoteAnimation();
+            }
+
+            finishSurfaceBehindRemoteAnimation(cancelled);
+            mSurfaceBehindRemoteAnimationRequested = false;
+            mKeyguardUnlockAnimationControllerLazy.get().notifyFinishedKeyguardExitAnimation();
+        });
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 558f0e6..d1fe7d4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.media.dagger;
 
+import android.app.Service;
 import android.content.Context;
 import android.view.WindowManager;
 
@@ -30,6 +31,7 @@
 import com.android.systemui.media.taptotransfer.MediaTttFlags;
 import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
 import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
+import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService;
 import com.android.systemui.statusbar.commandline.CommandRegistry;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
@@ -38,8 +40,11 @@
 
 import javax.inject.Named;
 
+import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
 
 /** Dagger module for the media package. */
 @Module
@@ -128,4 +133,10 @@
                         mediaTttChipControllerReceiver,
                         mainExecutor));
     }
+
+    /** Inject into MediaTttSenderService. */
+    @Binds
+    @IntoMap
+    @ClassKey(MediaTttSenderService.class)
+    Service bindMediaTttSenderService(MediaTttSenderService service);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
index 5a86723..460d38f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -16,9 +16,14 @@
 
 package com.android.systemui.media.taptotransfer
 
+import android.content.ComponentName
 import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
 import android.graphics.Color
 import android.graphics.drawable.Icon
+import android.media.MediaRoute2Info
+import android.os.IBinder
 import android.util.Log
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.R
@@ -27,9 +32,12 @@
 import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
 import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver
 import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender
-import com.android.systemui.media.taptotransfer.sender.MoveCloserToTransfer
+import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService
+import com.android.systemui.media.taptotransfer.sender.MoveCloserToStartCast
 import com.android.systemui.media.taptotransfer.sender.TransferInitiated
 import com.android.systemui.media.taptotransfer.sender.TransferSucceeded
+import com.android.systemui.shared.mediattt.DeviceInfo
+import com.android.systemui.shared.mediattt.IDeviceSenderCallback
 import com.android.systemui.statusbar.commandline.Command
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.util.concurrency.DelayableExecutor
@@ -44,11 +52,14 @@
 @SysUISingleton
 class MediaTttCommandLineHelper @Inject constructor(
     commandRegistry: CommandRegistry,
-    context: Context,
+    private val context: Context,
     private val mediaTttChipControllerSender: MediaTttChipControllerSender,
     private val mediaTttChipControllerReceiver: MediaTttChipControllerReceiver,
     @Main private val mainExecutor: DelayableExecutor,
 ) {
+    private var senderCallback: IDeviceSenderCallback? = null
+    private val senderServiceConnection = SenderServiceConnection()
+
     private val appIconDrawable =
         Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also {
             it.setTint(Color.YELLOW)
@@ -68,14 +79,20 @@
     inner class AddChipCommandSender : Command {
         override fun execute(pw: PrintWriter, args: List<String>) {
             val otherDeviceName = args[0]
+            val mediaInfo = MediaRoute2Info.Builder("id", "Test Name")
+                .addFeature("feature")
+                .build()
+            val otherDeviceInfo = DeviceInfo(otherDeviceName)
+
             when (args[1]) {
-                MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME -> {
-                    mediaTttChipControllerSender.displayChip(
-                        MoveCloserToTransfer(
-                            appIconDrawable, APP_ICON_CONTENT_DESCRIPTION, otherDeviceName
-                        )
-                    )
+                MOVE_CLOSER_TO_START_CAST_COMMAND_NAME -> {
+                    runOnService { senderCallback ->
+                        senderCallback.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo)
+                    }
                 }
+
+                // TODO(b/203800643): Migrate other commands to invoke the service instead of the
+                //   controller.
                 TRANSFER_INITIATED_COMMAND_NAME -> {
                     val futureTask = FutureTask { fakeUndoRunnable }
                     mediaTttChipControllerSender.displayChip(
@@ -101,7 +118,7 @@
                 }
                 else -> {
                     pw.println("Chip type must be one of " +
-                            "$MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME, " +
+                            "$MOVE_CLOSER_TO_START_CAST_COMMAND_NAME, " +
                             "$TRANSFER_INITIATED_COMMAND_NAME, " +
                             TRANSFER_SUCCEEDED_COMMAND_NAME
                     )
@@ -114,19 +131,40 @@
                     "$ADD_CHIP_COMMAND_SENDER_TAG <deviceName> <chipStatus>"
             )
         }
+
+        private fun runOnService(command: SenderCallbackCommand) {
+            val currentServiceCallback = senderCallback
+            if (currentServiceCallback != null) {
+                command.run(currentServiceCallback)
+            } else {
+                bindService(command)
+            }
+        }
+
+        private fun bindService(command: SenderCallbackCommand) {
+            senderServiceConnection.pendingCommand = command
+            val binding = context.bindService(
+                Intent(context, MediaTttSenderService::class.java),
+                senderServiceConnection,
+                Context.BIND_AUTO_CREATE
+            )
+            Log.i(TAG, "Starting service binding? $binding")
+        }
     }
 
     /** A command to REMOVE the media ttt chip on the SENDER device. */
     inner class RemoveChipCommandSender : Command {
         override fun execute(pw: PrintWriter, args: List<String>) {
             mediaTttChipControllerSender.removeChip()
+            if (senderCallback != null) {
+                context.unbindService(senderServiceConnection)
+            }
         }
         override fun help(pw: PrintWriter) {
             pw.println("Usage: adb shell cmd statusbar $REMOVE_CHIP_COMMAND_SENDER_TAG")
         }
     }
 
-
     /** A command to DISPLAY the media ttt chip on the RECEIVER device. */
     inner class AddChipCommandReceiver : Command {
         override fun execute(pw: PrintWriter, args: List<String>) {
@@ -149,6 +187,29 @@
         }
     }
 
+    /** A service connection for [IDeviceSenderCallback]. */
+    private inner class SenderServiceConnection : ServiceConnection {
+        // A command that should be run when the service gets connected.
+        var pendingCommand: SenderCallbackCommand? = null
+
+        override fun onServiceConnected(className: ComponentName, service: IBinder) {
+            val newCallback = IDeviceSenderCallback.Stub.asInterface(service)
+            senderCallback = newCallback
+            pendingCommand?.run(newCallback)
+            pendingCommand = null
+        }
+
+        override fun onServiceDisconnected(className: ComponentName) {
+            senderCallback = null
+        }
+    }
+
+    /** An interface defining a command that should be run on the sender callback. */
+    private fun interface SenderCallbackCommand {
+        /** Runs the command on the provided [senderCallback]. */
+        fun run(senderCallback: IDeviceSenderCallback)
+    }
+
     private val fakeUndoRunnable = Runnable {
         Log.i(TAG, "Undo runnable triggered")
     }
@@ -163,7 +224,7 @@
 @VisibleForTesting
 const val REMOVE_CHIP_COMMAND_RECEIVER_TAG = "media-ttt-chip-remove-receiver"
 @VisibleForTesting
-val MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME = MoveCloserToTransfer::class.simpleName!!
+val MOVE_CLOSER_TO_START_CAST_COMMAND_NAME = MoveCloserToStartCast::class.simpleName!!
 @VisibleForTesting
 val TRANSFER_INITIATED_COMMAND_NAME = TransferInitiated::class.simpleName!!
 @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index b1f6faa..dd434e7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -40,17 +40,18 @@
 ) : MediaTttChipState(appIconDrawable, appIconContentDescription)
 
 /**
- * A state representing that the two devices are close but not close enough to initiate a transfer.
- * The chip will instruct the user to move closer in order to initiate the transfer.
+ * A state representing that the two devices are close but not close enough to *start* a cast to
+ * the receiver device. The chip will instruct the user to move closer in order to initiate the
+ * transfer to the receiver.
  */
-class MoveCloserToTransfer(
+class MoveCloserToStartCast(
     appIconDrawable: Drawable,
     appIconContentDescription: String,
     otherDeviceName: String,
 ) : ChipStateSender(
     appIconDrawable,
     appIconContentDescription,
-    R.string.media_move_closer_to_transfer,
+    R.string.media_move_closer_to_start_cast,
     otherDeviceName
 )
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt
new file mode 100644
index 0000000..b56a699
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt
@@ -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.systemui.media.taptotransfer.sender
+
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.graphics.Color
+import android.graphics.drawable.Icon
+import android.media.MediaRoute2Info
+import android.os.IBinder
+import com.android.systemui.R
+import com.android.systemui.shared.mediattt.DeviceInfo
+import com.android.systemui.shared.mediattt.IDeviceSenderCallback
+import javax.inject.Inject
+
+/**
+ * Service that allows external handlers to trigger the media chip on the sender device.
+ */
+class MediaTttSenderService @Inject constructor(
+    context: Context,
+    val controller: MediaTttChipControllerSender
+) : Service() {
+
+    // TODO(b/203800643): Add logging when callbacks trigger.
+    private val binder: IBinder = object : IDeviceSenderCallback.Stub() {
+        override fun closeToReceiverToStartCast(
+            mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
+        ) {
+            this@MediaTttSenderService.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo)
+        }
+    }
+
+    // TODO(b/203800643): Use the app icon from the media info instead of a fake one.
+    private val fakeAppIconDrawable =
+        Icon.createWithResource(context, R.drawable.ic_avatar_user).loadDrawable(context).also {
+            it.setTint(Color.YELLOW)
+        }
+
+    override fun onBind(intent: Intent?): IBinder = binder
+
+    private fun closeToReceiverToStartCast(
+        mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
+    ) {
+        val chipState = MoveCloserToStartCast(
+            appIconDrawable = fakeAppIconDrawable,
+            appIconContentDescription = mediaInfo.name.toString(),
+            otherDeviceName = otherDeviceInfo.name
+        )
+        controller.displayChip(chipState)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 5fbdd88..ac816ba 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.navigationbar;
 
+import static android.inputmethodservice.InputMethodService.canImeRenderGesturalNavButtons;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
 
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
@@ -185,6 +186,17 @@
     @Nullable
     private Rect mOrientedHandleSamplingRegion;
 
+    /**
+     * {@code true} if the IME can render the back button and the IME switcher button.
+     *
+     * <p>The value must be used when and only when
+     * {@link com.android.systemui.shared.system.QuickStepContract#isGesturalMode(int)} returns
+     * {@code true}</p>
+     *
+     * <p>Cache the value here for better performance.</p>
+     */
+    private final boolean mImeCanRenderGesturalNavButtons = canImeRenderGesturalNavButtons();
+
     private class NavTransitionListener implements TransitionListener {
         private boolean mBackTransitioning;
         private boolean mHomeAppearing;
@@ -760,9 +772,14 @@
 
         updateRecentsIcon();
 
+        boolean isImeRenderingNavButtons = isGesturalMode(mNavBarMode)
+                && mImeCanRenderGesturalNavButtons;
+
         // Update IME button visibility, a11y and rotate button always overrides the appearance
-        mContextualButtonGroup.setButtonVisibility(R.id.ime_switcher,
-                (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0);
+        boolean disableImeSwitcher =
+                (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) == 0
+                || isImeRenderingNavButtons;
+        mContextualButtonGroup.setButtonVisibility(R.id.ime_switcher, !disableImeSwitcher);
 
         mBarTransitions.reapplyDarkIntensity();
 
@@ -777,7 +794,8 @@
                 && ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
 
         boolean disableBack = !useAltBack && (mEdgeBackGestureHandler.isHandlingGestures()
-                || ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0));
+                || ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0))
+                || isImeRenderingNavButtons;
 
         // When screen pinning, don't hide back and home when connected service or back and
         // recents buttons when disconnected from launcher service in screen pinning mode,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
index 11c4949..bd2f64b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
@@ -59,6 +59,7 @@
                                     R.dimen.qs_tile_service_request_tile_width),
                             context.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size)
                     )
+                    isSelected = true
         }
         val spacing = 0
         setView(ll, spacing, spacing, spacing, spacing / 2)
@@ -68,12 +69,17 @@
         val tile = QSTileViewImpl(context, QSIconViewImpl(context), true)
         val state = QSTile.BooleanState().apply {
             label = tileData.label
+            handlesLongClick = false
             icon = tileData.icon?.loadDrawable(context)?.let {
                 QSTileImpl.DrawableIcon(it)
             } ?: ResourceIcon.get(R.drawable.android)
         }
         tile.onStateChanged(state)
-        tile.isSelected = true
+        tile.post {
+            tile.stateDescription = ""
+            tile.isClickable = false
+            tile.isSelected = true
+        }
         return tile
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/FloatingWindowUtil.java b/packages/SystemUI/src/com/android/systemui/screenshot/FloatingWindowUtil.java
new file mode 100644
index 0000000..3dec387
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/FloatingWindowUtil.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.util.DisplayMetrics;
+import android.view.Window;
+import android.view.WindowManager;
+
+import com.android.internal.policy.PhoneWindow;
+
+/**
+ * Utility methods for setting up a floating window
+ */
+public class FloatingWindowUtil {
+
+    /**
+     * Convert input dp to pixels given DisplayMetrics
+     */
+    public static float dpToPx(DisplayMetrics metrics, float dp) {
+        return dp * metrics.densityDpi / (float) DisplayMetrics.DENSITY_DEFAULT;
+    }
+
+    /**
+     * Sets up window params for a floating window
+     */
+    public static WindowManager.LayoutParams getFloatingWindowParams() {
+        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                MATCH_PARENT, MATCH_PARENT, /* xpos */ 0, /* ypos */ 0, TYPE_SCREENSHOT,
+                WindowManager.LayoutParams.FLAG_FULLSCREEN
+                        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+                        | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+                PixelFormat.TRANSLUCENT);
+        params.layoutInDisplayCutoutMode =
+                WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        params.setFitInsetsTypes(0);
+        // This is needed to let touches pass through outside the touchable areas
+        params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+        return params;
+    }
+
+    /**
+     * Constructs a transparent floating window
+     */
+    public static PhoneWindow getFloatingWindow(Context context) {
+        PhoneWindow window = new PhoneWindow(context);
+        window.requestFeature(Window.FEATURE_NO_TITLE);
+        window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS);
+        window.setBackgroundDrawableResource(android.R.color.transparent);
+        return window;
+    }
+
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
index 4a1aa16..6c01f0e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
@@ -70,19 +70,28 @@
         super.setPressed(mIsPending || pressed);
     }
 
-    void setIcon(Icon icon, boolean tint) {
+    /**
+     * Set chip icon and whether to tint with theme color
+     */
+    public void setIcon(Icon icon, boolean tint) {
         mIconView.setImageIcon(icon);
         if (!tint) {
             mIconView.setImageTintList(null);
         }
     }
 
-    void setText(CharSequence text) {
+    /**
+     * Set chip text
+     */
+    public void setText(CharSequence text) {
         mTextView.setText(text);
         updatePadding(text.length() > 0);
     }
 
-    void setPendingIntent(PendingIntent intent, Runnable finisher) {
+    /**
+     * Set PendingIntent to be sent and Runnable to be run, when chip is clicked
+     */
+    public void setPendingIntent(PendingIntent intent, Runnable finisher) {
         setOnClickListener(v -> {
             try {
                 intent.send();
@@ -93,7 +102,10 @@
         });
     }
 
-    void setIsPending(boolean isPending) {
+    /**
+     * Set pressed state of chip (to be used when chip is clicked before underlying intent is ready)
+     */
+    public void setIsPending(boolean isPending) {
         mIsPending = isPending;
         setPressed(mIsPending);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index ce571e5..83d8d19 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -18,7 +18,6 @@
 
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
 
 import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
@@ -41,23 +40,21 @@
 import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks;
 import android.app.ICompatCameraControlCallback;
 import android.app.Notification;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.Insets;
-import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.media.MediaActionSound;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
 import android.os.RemoteException;
 import android.provider.Settings;
 import android.util.DisplayMetrics;
@@ -76,7 +73,6 @@
 import android.view.View;
 import android.view.ViewRootImpl;
 import android.view.ViewTreeObserver;
-import android.view.Window;
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
@@ -90,6 +86,7 @@
 import com.android.internal.policy.PhoneWindow;
 import com.android.settingslib.applications.InterestingConfigChanges;
 import com.android.systemui.R;
+import com.android.systemui.clipboardoverlay.ClipboardOverlayController;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
 import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
@@ -235,13 +232,11 @@
     static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
     static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip";
 
-
-    private static final int MESSAGE_CORNER_TIMEOUT = 2;
-    private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
-
     // From WizardManagerHelper.java
     private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
 
+    private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
+
     private final WindowContext mContext;
     private final ScreenshotNotificationsController mNotificationsController;
     private final ScreenshotSmartActions mScreenshotSmartActions;
@@ -260,6 +255,7 @@
     private final ScrollCaptureController mScrollCaptureController;
     private final LongScreenshotData mLongScreenshotHolder;
     private final boolean mIsLowRamDevice;
+    private final TimeoutHandler mScreenshotHandler;
 
     private ScreenshotView mScreenshotView;
     private Bitmap mScreenBitmap;
@@ -270,24 +266,8 @@
     private Animator mScreenshotAnimation;
     private RequestCallback mCurrentRequestCallback;
     private String mPackageName = "";
+    private BroadcastReceiver mCopyBroadcastReceiver;
 
-    private final Handler mScreenshotHandler = new Handler(Looper.getMainLooper()) {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MESSAGE_CORNER_TIMEOUT:
-                    if (DEBUG_UI) {
-                        Log.d(TAG, "Corner timeout hit");
-                    }
-                    mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT, 0,
-                            mPackageName);
-                    ScreenshotController.this.dismissScreenshot(false);
-                    break;
-                default:
-                    break;
-            }
-        }
-    };
 
     /** Tracks config changes that require re-creating UI */
     private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
@@ -309,7 +289,8 @@
             @Main Executor mainExecutor,
             ScrollCaptureController scrollCaptureController,
             LongScreenshotData longScreenshotHolder,
-            ActivityManager activityManager) {
+            ActivityManager activityManager,
+            TimeoutHandler timeoutHandler) {
         mScreenshotSmartActions = screenshotSmartActions;
         mNotificationsController = screenshotNotificationsController;
         mScrollCaptureClient = scrollCaptureClient;
@@ -321,6 +302,17 @@
         mIsLowRamDevice = activityManager.isLowRamDevice();
         mBgExecutor = Executors.newSingleThreadExecutor();
 
+        mScreenshotHandler = timeoutHandler;
+        mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
+        mScreenshotHandler.setOnTimeoutRunnable(() -> {
+            if (DEBUG_UI) {
+                Log.d(TAG, "Corner timeout hit");
+            }
+            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT, 0,
+                    mPackageName);
+            ScreenshotController.this.dismissScreenshot(false);
+        });
+
         mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
         final Context displayContext = context.createDisplayContext(getDefaultDisplay());
         mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
@@ -329,27 +321,11 @@
         mAccessibilityManager = AccessibilityManager.getInstance(mContext);
 
         // Setup the window that we are going to use
-        mWindowLayoutParams = new WindowManager.LayoutParams(
-                MATCH_PARENT, MATCH_PARENT, /* xpos */ 0, /* ypos */ 0, TYPE_SCREENSHOT,
-                WindowManager.LayoutParams.FLAG_FULLSCREEN
-                        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
-                        | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
-                PixelFormat.TRANSLUCENT);
+        mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
         mWindowLayoutParams.setTitle("ScreenshotAnimation");
-        mWindowLayoutParams.layoutInDisplayCutoutMode =
-                WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        mWindowLayoutParams.setFitInsetsTypes(0);
-        // This is needed to let touches pass through outside the touchable areas
-        mWindowLayoutParams.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 
-        mWindow = new PhoneWindow(mContext);
+        mWindow = FloatingWindowUtil.getFloatingWindow(mContext);
         mWindow.setWindowManager(mWindowManager, null, null);
-        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
-        mWindow.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS);
-        mWindow.setBackgroundDrawableResource(android.R.color.transparent);
 
         mConfigChanges.applyNewConfig(context.getResources());
         reloadAssets();
@@ -357,6 +333,18 @@
         // Setup the Camera shutter sound
         mCameraSound = new MediaActionSound();
         mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
+
+        mCopyBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (ClipboardOverlayController.COPY_OVERLAY_ACTION.equals(intent.getAction())) {
+                    dismissScreenshot(false);
+                }
+            }
+        };
+        mContext.registerReceiver(mCopyBroadcastReceiver, new IntentFilter(
+                        ClipboardOverlayController.COPY_OVERLAY_ACTION),
+                ClipboardOverlayController.SELF_PERMISSION, null);
     }
 
     void takeScreenshotFullscreen(ComponentName topComponent, Consumer<Uri> finisher,
@@ -424,7 +412,7 @@
             }
             return;
         }
-        cancelTimeout();
+        mScreenshotHandler.cancelTimeout();
         if (immediate) {
             finishDismiss();
         } else {
@@ -440,6 +428,7 @@
      * Release the constructed window context.
      */
     void releaseContext() {
+        mContext.unregisterReceiver(mCopyBroadcastReceiver);
         mContext.release();
         mCameraSound.release();
         mBgExecutor.shutdownNow();
@@ -462,7 +451,7 @@
                 if (DEBUG_INPUT) {
                     Log.d(TAG, "onUserInteraction");
                 }
-                resetTimeout();
+                mScreenshotHandler.resetTimeout();
             }
 
             @Override
@@ -517,6 +506,9 @@
         }
 
         saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, topComponent, true);
+
+        mContext.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION),
+                ClipboardOverlayController.SELF_PERMISSION);
     }
 
     private Bitmap captureScreenshot(Rect crop) {
@@ -651,7 +643,7 @@
         // ignore system bar insets for the purpose of window layout
         mWindow.getDecorView().setOnApplyWindowInsetsListener(
                 (v, insets) -> WindowInsets.CONSUMED);
-        cancelTimeout(); // restarted after animation
+        mScreenshotHandler.cancelTimeout(); // restarted after animation
     }
 
     private void requestScrollCapture() {
@@ -878,7 +870,7 @@
         }
         mScreenshotView.reset();
         removeWindow();
-        cancelTimeout();
+        mScreenshotHandler.cancelTimeout();
     }
 
     /**
@@ -905,30 +897,6 @@
         mSaveInBgTask.execute();
     }
 
-    private void cancelTimeout() {
-        if (DEBUG_DISMISS) {
-            Log.d(TAG, "cancel timeout");
-        }
-        mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
-    }
-
-    private void resetTimeout() {
-        cancelTimeout();
-
-        AccessibilityManager accessibilityManager = (AccessibilityManager)
-                mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
-        long timeoutMs = accessibilityManager.getRecommendedTimeoutMillis(
-                SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS,
-                AccessibilityManager.FLAG_CONTENT_CONTROLS);
-
-        mScreenshotHandler.sendMessageDelayed(
-                mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
-                timeoutMs);
-        if (DEBUG_DISMISS) {
-            Log.d(TAG, "dismiss timeout: " + timeoutMs + " ms");
-        }
-
-    }
 
     /**
      * Sets up the action shade and its entrance animation, once we get the screenshot URI.
@@ -939,7 +907,7 @@
             Log.d(TAG, "Showing UI actions");
         }
 
-        resetTimeout();
+        mScreenshotHandler.resetTimeout();
 
         if (imageData.uri != null) {
             mScreenshotHandler.post(() -> {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 7bcaf5f..e5649a1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -222,7 +222,6 @@
                     }
                 });
         mSwipeDetector.setIsLongpressEnabled(false);
-        mSwipeDismissHandler = new SwipeDismissHandler();
         addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View v) {
@@ -244,7 +243,7 @@
      * Called to display the scroll action chip when support is detected.
      *
      * @param packageName the owning package of the window to be captured
-     * @param onClick the action to take when the chip is clicked.
+     * @param onClick     the action to take when the chip is clicked.
      */
     public void showScrollChip(String packageName, Runnable onClick) {
         if (DEBUG_SCROLL) {
@@ -273,10 +272,12 @@
 
         final Rect tmpRect = new Rect();
         mScreenshotPreview.getBoundsOnScreen(tmpRect);
-        tmpRect.inset((int) dpToPx(-SWIPE_PADDING_DP), (int) dpToPx(-SWIPE_PADDING_DP));
+        tmpRect.inset((int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
         touchRegion.op(tmpRect, Region.Op.UNION);
         mActionsContainerBackground.getBoundsOnScreen(tmpRect);
-        tmpRect.inset((int) dpToPx(-SWIPE_PADDING_DP), (int) dpToPx(-SWIPE_PADDING_DP));
+        tmpRect.inset((int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
         touchRegion.op(tmpRect, Region.Op.UNION);
         mDismissButton.getBoundsOnScreen(tmpRect);
         touchRegion.op(tmpRect, Region.Op.UNION);
@@ -365,7 +366,7 @@
         mEditChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_edit_chip));
         mScrollChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_scroll_chip));
 
-        int swipePaddingPx = (int) dpToPx(SWIPE_PADDING_DP);
+        int swipePaddingPx = (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, SWIPE_PADDING_DP);
         TouchDelegate previewDelegate = new TouchDelegate(
                 new Rect(swipePaddingPx, swipePaddingPx, swipePaddingPx, swipePaddingPx),
                 mScreenshotPreview);
@@ -390,6 +391,24 @@
         // Get focus so that the key events go to the layout.
         setFocusableInTouchMode(true);
         requestFocus();
+
+        mSwipeDismissHandler = new SwipeDismissHandler(mContext, mScreenshotStatic,
+                new SwipeDismissHandler.SwipeDismissCallbacks() {
+                    @Override
+                    public void onInteraction() {
+                        mCallbacks.onUserInteraction();
+                    }
+
+                    @Override
+                    public void onDismiss() {
+                        if (DEBUG_DISMISS) {
+                            Log.d(ScreenshotView.TAG, "dismiss triggered via swipe gesture");
+                        }
+                        mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, 0,
+                                mPackageName);
+                        mCallbacks.onDismiss();
+                    }
+                });
     }
 
     View getScreenshotPreview() {
@@ -859,8 +878,8 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
-                        mCallbacks.onDismiss();
-                    }
+                mCallbacks.onDismiss();
+            }
         });
         animSet.start();
     }
@@ -934,38 +953,7 @@
     }
 
     void animateDismissal() {
-        animateDismissal(createScreenshotTranslateDismissAnimation());
-    }
-
-    private void animateDismissal(Animator dismissAnimation) {
-        mDismissAnimation = dismissAnimation;
-        mDismissAnimation.addListener(new AnimatorListenerAdapter() {
-            private boolean mCancelled = false;
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                super.onAnimationCancel(animation);
-                if (DEBUG_ANIM) {
-                    Log.d(TAG, "Cancelled dismiss animation");
-                }
-                mCancelled = true;
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                super.onAnimationEnd(animation);
-                if (!mCancelled) {
-                    if (DEBUG_ANIM) {
-                        Log.d(TAG, "after dismiss animation, calling onDismissRunnable.run()");
-                    }
-                    mCallbacks.onDismiss();
-                }
-            }
-        });
-        if (DEBUG_ANIM) {
-            Log.d(TAG, "Starting dismiss animation");
-        }
-        mDismissAnimation.start();
+        mSwipeDismissHandler.dismiss();
     }
 
     void reset() {
@@ -979,6 +967,7 @@
             }
             mDismissAnimation.cancel();
         }
+        mSwipeDismissHandler.cancel();
         if (DEBUG_WINDOW) {
             Log.d(TAG, "removing OnComputeInternalInsetsListener");
         }
@@ -1042,8 +1031,8 @@
         xAnim.setInterpolator(mAccelerateInterpolator);
         xAnim.setDuration(SCREENSHOT_DISMISS_X_DURATION_MS);
         float deltaX = mDirectionLTR
-                    ? -1 * (mScreenshotPreviewBorder.getX() + mScreenshotPreviewBorder.getWidth())
-                    : (mDisplayMetrics.widthPixels - mScreenshotPreviewBorder.getX());
+                ? -1 * (mScreenshotPreviewBorder.getX() + mScreenshotPreviewBorder.getWidth())
+                : (mDisplayMetrics.widthPixels - mScreenshotPreviewBorder.getX());
         xAnim.addUpdateListener(animation -> {
             float currXDelta = MathUtils.lerp(0, deltaX, animation.getAnimatedFraction());
             mScreenshotStatic.setTranslationX(currXDelta);
@@ -1100,130 +1089,4 @@
             return insetDrawable;
         }
     }
-
-    private float dpToPx(float dp) {
-        return dp * mDisplayMetrics.densityDpi / (float) DisplayMetrics.DENSITY_DEFAULT;
-    }
-
-    class SwipeDismissHandler implements OnTouchListener {
-        // distance needed to register a dismissal
-        private static final float DISMISS_DISTANCE_THRESHOLD_DP = 20;
-
-        private final GestureDetector mGestureDetector;
-
-        private float mStartX;
-        // Keeps track of the most recent direction (between the last two move events).
-        // -1 for left; +1 for right.
-        private int mDirectionX;
-        private float mPreviousX;
-
-        SwipeDismissHandler() {
-            GestureDetector.OnGestureListener gestureListener = new SwipeDismissGestureListener();
-            mGestureDetector = new GestureDetector(mContext, gestureListener);
-        }
-
-        @Override
-        public boolean onTouch(View view, MotionEvent event) {
-            boolean gestureResult = mGestureDetector.onTouchEvent(event);
-            mCallbacks.onUserInteraction();
-            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
-                mStartX = event.getRawX();
-                mPreviousX = mStartX;
-                return true;
-            } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
-                if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
-                    return true;
-                }
-                if (isPastDismissThreshold()) {
-                    if (DEBUG_DISMISS) {
-                        Log.d(TAG, "dismiss triggered via swipe gesture");
-                    }
-                    mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, 0, mPackageName);
-                    animateDismissal(createSwipeDismissAnimation());
-                } else {
-                    // if we've moved, but not past the threshold, start the return animation
-                    if (DEBUG_DISMISS) {
-                        Log.d(TAG, "swipe gesture abandoned");
-                    }
-                    createSwipeReturnAnimation().start();
-                }
-                return true;
-            }
-            return gestureResult;
-        }
-
-        class SwipeDismissGestureListener extends GestureDetector.SimpleOnGestureListener {
-            @Override
-            public boolean onScroll(
-                    MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) {
-                mScreenshotStatic.setTranslationX(ev2.getRawX() - mStartX);
-                mDirectionX = (ev2.getRawX() < mPreviousX) ? -1 : 1;
-                mPreviousX = ev2.getRawX();
-                return true;
-            }
-
-            @Override
-            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
-                    float velocityY) {
-                if (mScreenshotStatic.getTranslationX() * velocityX > 0
-                        && (mDismissAnimation == null || !mDismissAnimation.isRunning())) {
-                    animateDismissal(createSwipeDismissAnimation(velocityX / (float) 1000));
-                    return true;
-                }
-                return false;
-            }
-        }
-
-        private boolean isPastDismissThreshold() {
-            float translationX = mScreenshotStatic.getTranslationX();
-            // Determines whether the absolute translation from the start is in the same direction
-            // as the current movement. For example, if the user moves most of the way to the right,
-            // but then starts dragging back left, we do not dismiss even though the absolute
-            // distance is greater than the threshold.
-            if (translationX * mDirectionX > 0) {
-                return Math.abs(translationX) >= dpToPx(DISMISS_DISTANCE_THRESHOLD_DP);
-            }
-            return false;
-        }
-
-        private ValueAnimator createSwipeDismissAnimation() {
-            return createSwipeDismissAnimation(1);
-        }
-
-        private ValueAnimator createSwipeDismissAnimation(float velocity) {
-            // velocity is measured in pixels per millisecond
-            velocity = Math.min(3, Math.max(1, velocity));
-            ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
-            float startX = mScreenshotStatic.getTranslationX();
-            // make sure the UI gets all the way off the screen in the direction of movement
-            // (the actions container background is guaranteed to be both the leftmost and
-            // rightmost UI element in LTR and RTL)
-            float finalX = startX < 0
-                    ? -1 * mActionsContainerBackground.getRight()
-                    : mDisplayMetrics.widthPixels;
-            float distance = Math.abs(finalX - startX);
-
-            anim.addUpdateListener(animation -> {
-                float translation = MathUtils.lerp(startX, finalX, animation.getAnimatedFraction());
-                mScreenshotStatic.setTranslationX(translation);
-                setAlpha(1 - animation.getAnimatedFraction());
-            });
-            anim.setDuration((long) (distance / Math.abs(velocity)));
-            return anim;
-        }
-
-        private ValueAnimator createSwipeReturnAnimation() {
-            ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
-            float startX = mScreenshotStatic.getTranslationX();
-            float finalX = 0;
-
-            anim.addUpdateListener(animation -> {
-                float translation = MathUtils.lerp(
-                        startX, finalX, animation.getAnimatedFraction());
-                mScreenshotStatic.setTranslationX(translation);
-            });
-
-            return anim;
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java b/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java
new file mode 100644
index 0000000..4e96003
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.MathUtils;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * Allows a view to be swipe-dismissed, or returned to its location if distance threshold is not met
+ */
+public class SwipeDismissHandler implements View.OnTouchListener {
+    private static final String TAG = "SwipeDismissHandler";
+
+    // distance needed to register a dismissal
+    private static final float DISMISS_DISTANCE_THRESHOLD_DP = 20;
+
+    /**
+     * Stores the callbacks when the view is interacted with or dismissed.
+     */
+    public interface SwipeDismissCallbacks {
+        /**
+         * Run when the view is interacted with (touched)
+         */
+        void onInteraction();
+
+        /**
+         * Run when the view is dismissed (the distance threshold is met), post-dismissal animation
+         */
+        void onDismiss();
+    }
+
+    private final View mView;
+    private final SwipeDismissCallbacks mCallbacks;
+    private final GestureDetector mGestureDetector;
+    private DisplayMetrics mDisplayMetrics;
+    private ValueAnimator mDismissAnimation;
+
+
+    private float mStartX;
+    // Keeps track of the most recent direction (between the last two move events).
+    // -1 for left; +1 for right.
+    private int mDirectionX;
+    private float mPreviousX;
+
+    public SwipeDismissHandler(Context context, View view, SwipeDismissCallbacks callbacks) {
+        mView = view;
+        mCallbacks = callbacks;
+        GestureDetector.OnGestureListener gestureListener = new SwipeDismissGestureListener();
+        mGestureDetector = new GestureDetector(context, gestureListener);
+        mDisplayMetrics = new DisplayMetrics();
+        context.getDisplay().getRealMetrics(mDisplayMetrics);
+    }
+
+    @Override
+    public boolean onTouch(View view, MotionEvent event) {
+        boolean gestureResult = mGestureDetector.onTouchEvent(event);
+        mCallbacks.onInteraction();
+        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+            mStartX = event.getRawX();
+            mPreviousX = mStartX;
+            return true;
+        } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+            if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
+                return true;
+            }
+            if (isPastDismissThreshold()) {
+                dismiss();
+            } else {
+                // if we've moved, but not past the threshold, start the return animation
+                if (DEBUG_DISMISS) {
+                    Log.d(TAG, "swipe gesture abandoned");
+                }
+                createSwipeReturnAnimation().start();
+            }
+            return true;
+        }
+        return gestureResult;
+    }
+
+    class SwipeDismissGestureListener extends GestureDetector.SimpleOnGestureListener {
+        @Override
+        public boolean onScroll(
+                MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) {
+            mView.setTranslationX(ev2.getRawX() - mStartX);
+            mDirectionX = (ev2.getRawX() < mPreviousX) ? -1 : 1;
+            mPreviousX = ev2.getRawX();
+            return true;
+        }
+
+        @Override
+        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+                float velocityY) {
+            if (mView.getTranslationX() * velocityX > 0
+                    && (mDismissAnimation == null || !mDismissAnimation.isRunning())) {
+                dismiss(velocityX / (float) 1000);
+                return true;
+            }
+            return false;
+        }
+    }
+
+    private boolean isPastDismissThreshold() {
+        float translationX = mView.getTranslationX();
+        // Determines whether the absolute translation from the start is in the same direction
+        // as the current movement. For example, if the user moves most of the way to the right,
+        // but then starts dragging back left, we do not dismiss even though the absolute
+        // distance is greater than the threshold.
+        if (translationX * mDirectionX > 0) {
+            return Math.abs(translationX) >= FloatingWindowUtil.dpToPx(mDisplayMetrics,
+                    DISMISS_DISTANCE_THRESHOLD_DP);
+        }
+        return false;
+    }
+
+    /**
+     * Cancel the currently-running dismissal animation, if any.
+     */
+    public void cancel() {
+        if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
+            mDismissAnimation.cancel();
+        }
+    }
+
+    /**
+     * Start dismissal animation (will run onDismiss callback when animation complete)
+     */
+    public void dismiss() {
+        dismiss(1);
+    }
+
+    private void dismiss(float velocity) {
+        mDismissAnimation = createSwipeDismissAnimation(velocity);
+        mDismissAnimation.addListener(new AnimatorListenerAdapter() {
+            private boolean mCancelled;
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                super.onAnimationCancel(animation);
+                mCancelled = true;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                if (!mCancelled) {
+                    mCallbacks.onDismiss();
+                }
+            }
+        });
+        mDismissAnimation.start();
+    }
+
+    private ValueAnimator createSwipeDismissAnimation(float velocity) {
+        // velocity is measured in pixels per millisecond
+        velocity = Math.min(3, Math.max(1, velocity));
+        ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+        float startX = mView.getTranslationX();
+        // make sure the UI gets all the way off the screen in the direction of movement
+        // (the actions container background is guaranteed to be both the leftmost and
+        // rightmost UI element in LTR and RTL)
+        float finalX = startX <= 0 ? -1 * mView.getRight() : mDisplayMetrics.widthPixels;
+        float distance = Math.abs(finalX - startX);
+
+        anim.addUpdateListener(animation -> {
+            float translation = MathUtils.lerp(startX, finalX, animation.getAnimatedFraction());
+            mView.setTranslationX(translation);
+            mView.setAlpha(1 - animation.getAnimatedFraction());
+        });
+        anim.setDuration((long) (distance / Math.abs(velocity)));
+        return anim;
+    }
+
+    private ValueAnimator createSwipeReturnAnimation() {
+        ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+        float startX = mView.getTranslationX();
+        float finalX = 0;
+
+        anim.addUpdateListener(animation -> {
+            float translation = MathUtils.lerp(
+                    startX, finalX, animation.getAnimatedFraction());
+            mView.setTranslationX(translation);
+        });
+
+        return anim;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 84e21e4..98e6bd1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -83,7 +83,7 @@
 
     /** Informs about coarse grained state of the Controller. */
     interface RequestCallback {
-        /** Respond to the current request indicating the screenshot request failed.*/
+        /** Respond to the current request indicating the screenshot request failed. */
         void reportError();
 
         /** The controller has completed handling this request UI has been removed */
@@ -230,7 +230,7 @@
                 return false;
         }
         return true;
-    };
+    }
 
     private static void sendComplete(Messenger target) {
         try {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
new file mode 100644
index 0000000..9156601
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.view.accessibility.AccessibilityManager;
+
+import javax.inject.Inject;
+
+/**
+ * Starts a configurable runnable on timeout. Can be cancelled. Used for automatically dismissing
+ * floating overlays.
+ */
+public class TimeoutHandler extends Handler {
+    private static final String TAG = "TimeoutHandler";
+
+    private static final int MESSAGE_CORNER_TIMEOUT = 2;
+    private static final int DEFAULT_TIMEOUT_MILLIS = 6000;
+
+    private final Context mContext;
+
+    private Runnable mOnTimeout;
+    private int mDefaultTimeout = DEFAULT_TIMEOUT_MILLIS;
+
+    @Inject
+    public TimeoutHandler(Context context) {
+        super(Looper.getMainLooper());
+        mContext = context;
+        mOnTimeout = () -> {
+        };
+    }
+
+    public void setOnTimeoutRunnable(Runnable onTimeout) {
+        mOnTimeout = onTimeout;
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case MESSAGE_CORNER_TIMEOUT:
+                mOnTimeout.run();
+                break;
+            default:
+                break;
+        }
+    }
+
+    /**
+     * Set the default timeout (if not overridden by accessibility)
+     */
+    public void setDefaultTimeoutMillis(int timeout) {
+        mDefaultTimeout = timeout;
+    }
+
+    /**
+     * Cancel the current timeout, if any. To reset the delayed runnable use resetTimeout instead.
+     */
+    public void cancelTimeout() {
+        if (DEBUG_DISMISS) {
+            Log.d(TAG, "cancel timeout");
+        }
+        removeMessages(MESSAGE_CORNER_TIMEOUT);
+    }
+
+    /**
+     * Reset the timeout.
+     */
+    public void resetTimeout() {
+        cancelTimeout();
+
+        AccessibilityManager accessibilityManager = (AccessibilityManager)
+                mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+        long timeoutMs = accessibilityManager.getRecommendedTimeoutMillis(
+                mDefaultTimeout,
+                AccessibilityManager.FLAG_CONTENT_CONTROLS);
+
+        sendMessageDelayed(obtainMessage(MESSAGE_CORNER_TIMEOUT), timeoutMs);
+        if (DEBUG_DISMISS) {
+            Log.d(TAG, "dismiss timeout: " + timeoutMs + " ms");
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index 5272a16..300c3a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -334,6 +334,8 @@
                 setUserSetupComplete(deviceProvisionedController.isCurrentUserSetup());
             }
         });
+        // Get initial user setup state
+        setUserSetupComplete(deviceProvisionedController.isCurrentUserSetup());
 
         WifiManager.ScanResultsCallback scanResultsCallback =
                 new WifiManager.ScanResultsCallback() {
@@ -994,6 +996,11 @@
     }
 
     @VisibleForTesting
+    boolean isUserSetup() {
+        return mUserSetup;
+    }
+
+    @VisibleForTesting
     boolean hasCorrectMobileControllers(List<SubscriptionInfo> allSubscriptions) {
         if (allSubscriptions.size() != mMobileSignalControllers.size()) {
             return false;
@@ -1137,6 +1144,7 @@
     /** */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("NetworkController state:");
+        pw.println("  mUserSetup=" + mUserSetup);
 
         pw.println("  - telephony ------");
         pw.print("  hasVoiceCallingFeature()=");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
index 43a75a5..ad97392 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -24,16 +24,18 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.util.traceSection
-import javax.inject.Inject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 
 /**
  * Responsible for building and applying the "shade node spec": the list (tree) of things that
  * currently populate the notification shade.
  */
-class ShadeViewManager constructor(
+class ShadeViewManager @AssistedInject constructor(
     context: Context,
-    listContainer: NotificationListContainer,
-    private val stackController: NotifStackController,
+    @Assisted listContainer: NotificationListContainer,
+    @Assisted private val stackController: NotifStackController,
     mediaContainerController: MediaContainerController,
     featureManager: NotificationSectionsFeatureManager,
     logger: ShadeViewDifferLogger,
@@ -68,20 +70,10 @@
     }
 }
 
-class ShadeViewManagerFactory @Inject constructor(
-    private val context: Context,
-    private val logger: ShadeViewDifferLogger,
-    private val mediaContainerController: MediaContainerController,
-    private val sectionsFeatureManager: NotificationSectionsFeatureManager,
-    private val viewBarn: NotifViewBarn
-) {
-    fun create(listContainer: NotificationListContainer, stackController: NotifStackController) =
-        ShadeViewManager(
-            context,
-            listContainer,
-            stackController,
-            mediaContainerController,
-            sectionsFeatureManager,
-            logger,
-            viewBarn)
+@AssistedFactory
+interface ShadeViewManagerFactory {
+    fun create(
+        listContainer: NotificationListContainer,
+        stackController: NotifStackController
+    ): ShadeViewManager
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index bfa4a24..dee1b33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.systemui.statusbar.phone;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index ec2d036..571c10b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -54,6 +54,7 @@
             isListening = false
             updateListeningState()
             keyguardUpdateMonitor.requestFaceAuth(true)
+            keyguardUpdateMonitor.requestActiveUnlock()
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
index 4de78f5..868efa0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
@@ -34,7 +34,7 @@
     private val displayMetrics: DisplayMetrics
 ) {
     fun logUnSuccessfulDragDown(startingChild: View?) {
-        val entry = (startingChild as ExpandableNotificationRow?)?.entry
+        val entry = (startingChild as? ExpandableNotificationRow)?.entry
         buffer.log(TAG, LogLevel.INFO, {
             str1 = entry?.key ?: "no entry"
         }, {
@@ -49,7 +49,7 @@
     }
 
     fun logDragDownStarted(startingChild: ExpandableView?) {
-        val entry = (startingChild as ExpandableNotificationRow?)?.entry
+        val entry = (startingChild as? ExpandableNotificationRow)?.entry
         buffer.log(TAG, LogLevel.INFO, {
             str1 = entry?.key ?: "no entry"
         }, {
@@ -58,7 +58,7 @@
     }
 
     fun logDraggedDownLockDownShade(startingChild: View?) {
-        val entry = (startingChild as ExpandableNotificationRow?)?.entry
+        val entry = (startingChild as? ExpandableNotificationRow)?.entry
         buffer.log(TAG, LogLevel.INFO, {
             str1 = entry?.key ?: "no entry"
         }, {
@@ -67,7 +67,7 @@
     }
 
     fun logDraggedDown(startingChild: View?, dragLengthY: Int) {
-        val entry = (startingChild as ExpandableNotificationRow?)?.entry
+        val entry = (startingChild as? ExpandableNotificationRow)?.entry
         buffer.log(TAG, LogLevel.INFO, {
             str1 = entry?.key ?: "no entry"
         }, {
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 a9b3927..981c843 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1359,10 +1359,14 @@
         // Things that mean we're not dismissing the keyguard, and should ignore this expansion:
         // - Keyguard isn't even visible.
         // - Keyguard is visible, but can't be dismissed (swiping up will show PIN/password prompt).
+        // - The SIM is locked, you can't swipe to unlock. If the SIM is locked but there is no
+        //   device lock set, canDismissLockScreen returns true even though you should not be able
+        //   to dismiss the lock screen until entering the SIM PIN.
         // - QS is expanded and we're swiping - swiping up now will hide QS, not dismiss the
         //   keyguard.
         if (!isKeyguardShowing()
                 || !mKeyguardStateController.canDismissLockScreen()
+                || mKeyguardViewMediator.isAnySimPinSecure()
                 || (mNotificationPanelViewController.isQsExpanded() && trackingTouch)) {
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 0ba7134..ea0dd72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -99,6 +99,7 @@
 
             override fun onAnimationEnd(animation: Animator?) {
                 lightRevealAnimationPlaying = false
+                interactionJankMonitor.end(CUJ_SCREEN_OFF)
             }
 
             override fun onAnimationStart(animation: Animator?) {
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLogger.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLogger.kt
new file mode 100644
index 0000000..1451c03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLogger.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.unfold
+
+import com.android.internal.util.FrameworkStatsLog
+
+/** Logs fold state changes. */
+class FoldStateLogger(private val foldStateLoggingProvider: FoldStateLoggingProvider) :
+    FoldStateLoggingProvider.FoldStateLoggingListener {
+
+    fun init() {
+        foldStateLoggingProvider.addCallback(this)
+    }
+
+    override fun onFoldUpdate(foldStateUpdate: FoldStateChange) {
+        FrameworkStatsLog.write(
+            FrameworkStatsLog.FOLD_STATE_DURATION_REPORTED,
+            foldStateUpdate.previous,
+            foldStateUpdate.current,
+            foldStateUpdate.dtMillis)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index f2c1561..c2fd34c 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -102,6 +102,15 @@
 
     @Provides
     @Singleton
+    fun providesFoldStateLogger(
+        optionalFoldStateLoggingProvider: Optional<FoldStateLoggingProvider>
+    ): Optional<FoldStateLogger> =
+        optionalFoldStateLoggingProvider.map { FoldStateLoggingProvider ->
+            FoldStateLogger(FoldStateLoggingProvider)
+        }
+
+    @Provides
+    @Singleton
     fun provideUnfoldTransitionConfig(context: Context): UnfoldTransitionConfig =
         createConfig(context)
 
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java
index aec14be..d10e890 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbContaminantActivity.java
@@ -70,6 +70,10 @@
         mEnableUsb.setText(getString(R.string.usb_disable_contaminant_detection));
         mGotIt.setText(getString(R.string.got_it));
         mLearnMore.setText(getString(R.string.learn_more));
+        if (getResources().getBoolean(
+                com.android.internal.R.bool.config_settingsHelpLinksEnabled)) {
+            mLearnMore.setVisibility(View.VISIBLE);
+        }
 
         mEnableUsb.setOnClickListener(this);
         mGotIt.setOnClickListener(this);
diff --git a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
index cbd6e86..0369d5b 100644
--- a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
+++ b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
@@ -110,6 +110,17 @@
     private Collection<String> getClassNamesFromClassPath() {
         ClassPathScanner scanner = new ClassPathScanner(mContext.getPackageCodePath());
 
+        ChainedClassNameFilter filter = makeClassNameFilter();
+
+        try {
+            return scanner.getClassPathEntries(filter);
+        } catch (IOException e) {
+            Log.e(getTag(), "Failed to scan classes", e);
+        }
+        return Collections.emptyList();
+    }
+
+    protected ChainedClassNameFilter makeClassNameFilter() {
         ChainedClassNameFilter filter = new ChainedClassNameFilter();
 
         filter.add(new ExternalClassNameFilter());
@@ -122,13 +133,7 @@
         // the main SystemUI process. Therefore, exclude this package
         // from the base class whitelist.
         filter.add(s -> !s.startsWith("com.android.systemui.screenshot"));
-
-        try {
-            return scanner.getClassPathEntries(filter);
-        } catch (IOException e) {
-            Log.e(TAG, "Failed to scan classes", e);
-        }
-        return Collections.emptyList();
+        return filter;
     }
 
     private String getClsStr() {
@@ -212,8 +217,12 @@
      * as loggable to limit log spam during normal use.
      */
     private void logDebug(String msg) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, msg);
+        if (Log.isLoggable(getTag(), Log.DEBUG)) {
+            Log.d(getTag(), msg);
         }
     }
+
+    protected String getTag() {
+        return TAG;
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
new file mode 100644
index 0000000..6bc6505
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
@@ -0,0 +1,194 @@
+/*
+ * 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.keyguard
+
+import android.hardware.biometrics.BiometricSourceType
+import org.mockito.Mockito.verify
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() {
+    @Mock
+    lateinit var uiEventLogger: UiEventLogger
+    @Mock
+    lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock
+    lateinit var dumpManager: DumpManager
+    @Mock
+    lateinit var strongAuthTracker: KeyguardUpdateMonitor.StrongAuthTracker
+
+    @Captor
+    lateinit var updateMonitorCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
+    lateinit var updateMonitorCallback: KeyguardUpdateMonitorCallback
+
+    lateinit var keyguardBiometricLockoutLogger: KeyguardBiometricLockoutLogger
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(keyguardUpdateMonitor.strongAuthTracker).thenReturn(strongAuthTracker)
+        keyguardBiometricLockoutLogger = KeyguardBiometricLockoutLogger(
+                mContext,
+                uiEventLogger,
+                keyguardUpdateMonitor,
+                dumpManager)
+    }
+
+    @Test
+    fun test_logsOnStart() {
+        // GIVEN is encrypted / lockdown before start
+        whenever(keyguardUpdateMonitor.isEncryptedOrLockdown(anyInt()))
+                .thenReturn(true)
+
+        // WHEN start
+        keyguardBiometricLockoutLogger.start()
+
+        // THEN encrypted / lockdown state is logged
+        verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
+                .PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN)
+    }
+
+    @Test
+    fun test_logTimeoutChange() {
+        keyguardBiometricLockoutLogger.start()
+        captureUpdateMonitorCallback()
+
+        // GIVEN primary auth required b/c timeout
+        whenever(strongAuthTracker.getStrongAuthForUser(anyInt()))
+                .thenReturn(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT)
+
+        // WHEN primary auth requirement changes
+        updateMonitorCallback.onStrongAuthStateChanged(0)
+
+        // THEN primary auth required state is logged
+        verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
+                .PRIMARY_AUTH_REQUIRED_TIMEOUT)
+    }
+
+    @Test
+    fun test_logUnattendedUpdate() {
+        keyguardBiometricLockoutLogger.start()
+        captureUpdateMonitorCallback()
+
+        // GIVEN primary auth required b/c unattended update
+        whenever(strongAuthTracker.getStrongAuthForUser(anyInt()))
+                .thenReturn(STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE)
+
+        // WHEN primary auth requirement changes
+        updateMonitorCallback.onStrongAuthStateChanged(0)
+
+        // THEN primary auth required state is logged
+        verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
+                .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE)
+    }
+
+    @Test
+    fun test_logMultipleChanges() {
+        keyguardBiometricLockoutLogger.start()
+        captureUpdateMonitorCallback()
+
+        // GIVEN primary auth required b/c timeout
+        whenever(strongAuthTracker.getStrongAuthForUser(anyInt()))
+                .thenReturn(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
+                        or STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE)
+
+        // WHEN primary auth requirement changes
+        updateMonitorCallback.onStrongAuthStateChanged(0)
+
+        // THEN primary auth required state is logged with all the reasons
+        verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
+                .PRIMARY_AUTH_REQUIRED_TIMEOUT)
+        verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
+                .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE)
+
+        // WHEN onStrongAuthStateChanged is called again
+        updateMonitorCallback.onStrongAuthStateChanged(0)
+
+        // THEN no more events are sent since there haven't been any changes
+        verifyNoMoreInteractions(uiEventLogger)
+    }
+
+    @Test
+    fun test_logFaceLockout() {
+        keyguardBiometricLockoutLogger.start()
+        captureUpdateMonitorCallback()
+
+        // GIVEN primary auth required b/c face lock
+        whenever(keyguardUpdateMonitor.isFaceLockedOut).thenReturn(true)
+
+        // WHEN lockout state changes
+        updateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE)
+
+        // THEN primary auth required state is logged
+        verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
+                .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT)
+
+        // WHEN face lockout is reset
+        whenever(keyguardUpdateMonitor.isFaceLockedOut).thenReturn(false)
+        updateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE)
+
+        // THEN primary auth required state is logged
+        verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
+                .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET)
+    }
+
+    @Test
+    fun test_logFingerprintLockout() {
+        keyguardBiometricLockoutLogger.start()
+        captureUpdateMonitorCallback()
+
+        // GIVEN primary auth required b/c fingerprint lock
+        whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(true)
+
+        // WHEN lockout state changes
+        updateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT)
+
+        // THEN primary auth required state is logged
+        verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
+                .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT)
+
+        // WHEN fingerprint lockout is reset
+        whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
+        updateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT)
+
+        // THEN primary auth required state is logged
+        verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
+                .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET)
+    }
+
+    fun captureUpdateMonitorCallback() {
+        verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallbackCaptor.capture())
+        updateMonitorCallback = updateMonitorCallbackCaptor.value
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 70792cf..7266e41 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -88,6 +88,7 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -173,6 +174,8 @@
     private InteractionJankMonitor mInteractionJankMonitor;
     @Mock
     private LatencyTracker mLatencyTracker;
+    @Mock
+    private FeatureFlags mFeatureFlags;
     @Captor
     private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
     // Direct executor
@@ -1105,7 +1108,7 @@
                     mRingerModeTracker, mBackgroundExecutor, mMainExecutor,
                     mStatusBarStateController, mLockPatternUtils,
                     mAuthController, mTelephonyListenerManager,
-                    mInteractionJankMonitor, mLatencyTracker);
+                    mInteractionJankMonitor, mLatencyTracker, mFeatureFlags);
             setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java
index 40549d69..8c20b24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java
@@ -29,8 +29,10 @@
 import com.android.systemui.utils.leaks.LeakCheckedTest.SysuiLeakCheck;
 
 import org.junit.After;
+import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.Rule;
+import org.mockito.Mockito;
 
 public abstract class SysuiBaseFragmentTest extends BaseFragmentTest {
 
@@ -78,6 +80,11 @@
         SystemUIFactory.cleanup();
     }
 
+    @AfterClass
+    public static void mockitoTeardown() {
+        Mockito.framework().clearInlineMocks();
+    }
+
     @Override
     protected SysuiTestableContext getContext() {
         return new SysuiTestableContext(InstrumentationRegistry.getContext(), mLeakCheck);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
index 2d51092..254fc59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
@@ -25,6 +25,7 @@
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN
+import android.hardware.biometrics.SensorLocationInternal
 import android.hardware.biometrics.SensorProperties
 import android.hardware.display.DisplayManager
 import android.hardware.display.DisplayManagerGlobal
@@ -52,6 +53,7 @@
 import com.android.systemui.recents.OverviewProxyService
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -75,6 +77,12 @@
 private const val DISPLAY_ID = 2
 private const val SENSOR_ID = 1
 
+private const val DISPLAY_SIZE_X = 800
+private const val DISPLAY_SIZE_Y = 900
+
+private val X_LOCATION = SensorLocationInternal("", 540, 0, 20)
+private val Y_LOCATION = SensorLocationInternal("", 0, 1500, 22)
+
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
@@ -101,6 +109,8 @@
     lateinit var handler: Handler
     @Captor
     lateinit var overlayCaptor: ArgumentCaptor<View>
+    @Captor
+    lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
 
     private val executor = FakeExecutor(FakeSystemClock())
     private lateinit var overlayController: ISidefpsController
@@ -125,6 +135,17 @@
                 this
             }
         }
+        `when`(windowManager.maximumWindowMetrics).thenReturn(
+            WindowMetrics(Rect(0, 0, DISPLAY_SIZE_X, DISPLAY_SIZE_Y), WindowInsets.CONSUMED)
+        )
+    }
+
+    private fun testWithDisplay(
+        initInfo: DisplayInfo.() -> Unit = {},
+        locations: List<SensorLocationInternal> = listOf(X_LOCATION),
+        windowInsets: WindowInsets = insetsForSmallNavbar(),
+        block: () -> Unit
+    ) {
         `when`(fingerprintManager.sensorPropertiesInternal).thenReturn(
             listOf(
                 FingerprintSensorPropertiesInternal(
@@ -133,22 +154,21 @@
                     5 /* maxEnrollmentsPerUser */,
                     listOf() /* componentInfo */,
                     FingerprintSensorProperties.TYPE_POWER_BUTTON,
-                    true /* resetLockoutRequiresHardwareAuthToken */
+                    true /* resetLockoutRequiresHardwareAuthToken */,
+                    locations
                 )
             )
         )
-        `when`(windowManager.maximumWindowMetrics).thenReturn(
-            WindowMetrics(Rect(0, 0, 800, 800), WindowInsets.CONSUMED)
-        )
-    }
 
-    private fun testWithDisplay(initInfo: DisplayInfo.() -> Unit = {}, block: () -> Unit) {
         val displayInfo = DisplayInfo()
         displayInfo.initInfo()
         val dmGlobal = mock(DisplayManagerGlobal::class.java)
         val display = Display(dmGlobal, DISPLAY_ID, displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS)
         `when`(dmGlobal.getDisplayInfo(eq(DISPLAY_ID))).thenReturn(displayInfo)
         `when`(windowManager.defaultDisplay).thenReturn(display)
+        `when`(windowManager.currentWindowMetrics).thenReturn(
+            WindowMetrics(Rect(0, 0, DISPLAY_SIZE_X, DISPLAY_SIZE_Y), windowInsets)
+        )
 
         sideFpsController = SidefpsController(
             context.createDisplayContext(display), layoutInflater, fingerprintManager,
@@ -245,28 +265,71 @@
     }
 
     @Test
+    fun showsWithTaskbarOnY() = testWithDisplay(
+        { rotation = Surface.ROTATION_0 },
+        locations = listOf(Y_LOCATION)
+    ) {
+        hidesWithTaskbar(visible = true)
+    }
+
+    @Test
     fun showsWithTaskbar90() = testWithDisplay({ rotation = Surface.ROTATION_90 }) {
         hidesWithTaskbar(visible = true)
     }
 
     @Test
+    fun showsWithTaskbar90OnY() = testWithDisplay(
+        { rotation = Surface.ROTATION_90 },
+        locations = listOf(Y_LOCATION)
+    ) {
+        hidesWithTaskbar(visible = true)
+    }
+
+    @Test
     fun showsWithTaskbar180() = testWithDisplay({ rotation = Surface.ROTATION_180 }) {
         hidesWithTaskbar(visible = true)
     }
 
     @Test
-    fun showsWithTaskbarCollapsedDown() = testWithDisplay({ rotation = Surface.ROTATION_270 }) {
-        `when`(windowManager.currentWindowMetrics).thenReturn(
-            WindowMetrics(Rect(0, 0, 800, 800), insetsForSmallNavbar())
-        )
+    fun showsWithTaskbar270OnY() = testWithDisplay(
+        { rotation = Surface.ROTATION_270 },
+        locations = listOf(Y_LOCATION)
+    ) {
         hidesWithTaskbar(visible = true)
     }
 
     @Test
-    fun hidesWithTaskbarDown() = testWithDisplay({ rotation = Surface.ROTATION_270 }) {
-        `when`(windowManager.currentWindowMetrics).thenReturn(
-            WindowMetrics(Rect(0, 0, 800, 800), insetsForLargeNavbar())
-        )
+    fun showsWithTaskbarCollapsedDown() = testWithDisplay(
+        { rotation = Surface.ROTATION_270 },
+        windowInsets = insetsForSmallNavbar()
+    ) {
+        hidesWithTaskbar(visible = true)
+    }
+
+    @Test
+    fun showsWithTaskbarCollapsedDownOnY() = testWithDisplay(
+        { rotation = Surface.ROTATION_180 },
+        locations = listOf(Y_LOCATION),
+        windowInsets = insetsForSmallNavbar()
+    ) {
+        hidesWithTaskbar(visible = true)
+    }
+
+    @Test
+    fun hidesWithTaskbarDown() = testWithDisplay(
+        { rotation = Surface.ROTATION_180 },
+        locations = listOf(X_LOCATION),
+        windowInsets = insetsForLargeNavbar()
+    ) {
+        hidesWithTaskbar(visible = false)
+    }
+
+    @Test
+    fun hidesWithTaskbarDownOnY() = testWithDisplay(
+        { rotation = Surface.ROTATION_270 },
+        locations = listOf(Y_LOCATION),
+        windowInsets = insetsForLargeNavbar()
+    ) {
         hidesWithTaskbar(visible = false)
     }
 
@@ -281,6 +344,28 @@
         verify(windowManager, never()).removeView(any())
         verify(sidefpsView).visibility = if (visible) View.VISIBLE else View.GONE
     }
+
+    @Test
+    fun setsXAlign() = testWithDisplay {
+        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+        executor.runAllReady()
+
+        verify(windowManager).addView(any(), overlayViewParamsCaptor.capture())
+
+        assertThat(overlayViewParamsCaptor.value.x).isEqualTo(X_LOCATION.sensorLocationX)
+        assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
+    }
+
+    @Test
+    fun setYAlign() = testWithDisplay(locations = listOf(Y_LOCATION)) {
+        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+        executor.runAllReady()
+
+        verify(windowManager).addView(any(), overlayViewParamsCaptor.capture())
+
+        assertThat(overlayViewParamsCaptor.value.x).isEqualTo(DISPLAY_SIZE_X)
+        assertThat(overlayViewParamsCaptor.value.y).isEqualTo(Y_LOCATION.sensorLocationY)
+    }
 }
 
 private fun insetsForSmallNavbar() = insetsWithBottom(60)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
index 2d3757c..12096bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
@@ -17,6 +17,9 @@
 package com.android.systemui.controls.controller
 
 import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
 import android.os.UserHandle
 import android.service.controls.IControlsActionCallback
 import android.service.controls.IControlsProvider
@@ -43,6 +46,8 @@
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.`when`
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -57,8 +62,6 @@
     private lateinit var subscriberService: IControlsSubscriber.Stub
     @Mock
     private lateinit var service: IControlsProvider.Stub
-    @Mock
-    private lateinit var loadCallback: ControlsBindingController.LoadCallback
 
     @Captor
     private lateinit var wrapperCaptor: ArgumentCaptor<ControlActionWrapper>
@@ -75,7 +78,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        mContext.addMockService(componentName, service)
+        context.addMockService(componentName, service)
         executor = FakeExecutor(FakeSystemClock())
         `when`(service.asBinder()).thenCallRealMethod()
         `when`(service.queryLocalInterface(ArgumentMatchers.anyString())).thenReturn(service)
@@ -98,7 +101,36 @@
     fun testBindService() {
         manager.bindService()
         executor.runAllReady()
-        assertTrue(mContext.isBound(componentName))
+        assertTrue(context.isBound(componentName))
+    }
+
+    @Test
+    fun testNullBinding() {
+        val mockContext = mock(Context::class.java)
+        lateinit var serviceConnection: ServiceConnection
+        `when`(mockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer {
+            val component = (it.arguments[0] as Intent).component
+            if (component == componentName) {
+                serviceConnection = it.arguments[1] as ServiceConnection
+                serviceConnection.onNullBinding(component)
+                true
+            } else {
+                false
+            }
+        }
+
+        val nullManager = ControlsProviderLifecycleManager(
+                mockContext,
+                executor,
+                actionCallbackService,
+                UserHandle.of(0),
+                componentName
+        )
+
+        nullManager.bindService()
+        executor.runAllReady()
+
+        verify(mockContext).unbindService(serviceConnection)
     }
 
     @Test
@@ -109,7 +141,7 @@
         manager.unbindService()
         executor.runAllReady()
 
-        assertFalse(mContext.isBound(componentName))
+        assertFalse(context.isBound(componentName))
     }
 
     @Test
@@ -119,7 +151,7 @@
 
         verify(service).load(subscriberService)
 
-        assertTrue(mContext.isBound(componentName))
+        assertTrue(context.isBound(componentName))
     }
 
     @Test
@@ -129,7 +161,7 @@
 
         manager.unbindService()
         executor.runAllReady()
-        assertFalse(mContext.isBound(componentName))
+        assertFalse(context.isBound(componentName))
     }
 
     @Test
@@ -162,7 +194,7 @@
         manager.maybeBindAndSubscribe(list, subscriberService)
         executor.runAllReady()
 
-        assertTrue(mContext.isBound(componentName))
+        assertTrue(context.isBound(componentName))
         verify(service).subscribe(list, subscriberService)
     }
 
@@ -173,7 +205,7 @@
         manager.maybeBindAndSendAction(controlId, action)
         executor.runAllReady()
 
-        assertTrue(mContext.isBound(componentName))
+        assertTrue(context.isBound(componentName))
         verify(service).action(eq(controlId), capture(wrapperCaptor),
                 eq(actionCallbackService))
         assertEquals(action, wrapperCaptor.getValue().getWrappedAction())
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 8cd8e4d..c0b7271 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -16,12 +16,15 @@
 
 package com.android.systemui.dreams;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Intent;
 import android.os.IBinder;
+import android.service.dreams.DreamService;
 import android.service.dreams.IDreamOverlay;
 import android.service.dreams.IDreamOverlayCallback;
 import android.testing.AndroidTestingRunner;
@@ -195,4 +198,22 @@
         mService.onDestroy();
         verify(mDreamOverlayStateController).removeCallback(callbackCapture.getValue());
     }
+
+    @Test
+    public void testShouldShowComplicationsTrueByDefault() {
+        assertThat(mService.shouldShowComplications()).isTrue();
+
+        mService.onBind(new Intent());
+
+        assertThat(mService.shouldShowComplications()).isTrue();
+    }
+
+    @Test
+    public void testShouldShowComplicationsSetByIntentExtra() {
+        final Intent intent = new Intent();
+        intent.putExtra(DreamService.EXTRA_SHOW_COMPLICATIONS, false);
+        mService.onBind(intent);
+
+        assertThat(mService.shouldShowComplications()).isFalse();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
index dec5a10..4839bde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
@@ -16,23 +16,28 @@
 
 package com.android.systemui.media.taptotransfer
 
+import android.content.ComponentName
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver
 import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
-import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender
-import com.android.systemui.media.taptotransfer.sender.MoveCloserToTransfer
-import com.android.systemui.media.taptotransfer.sender.TransferInitiated
-import com.android.systemui.media.taptotransfer.sender.TransferSucceeded
+import com.android.systemui.media.taptotransfer.sender.*
+import com.android.systemui.shared.mediattt.DeviceInfo
+import com.android.systemui.shared.mediattt.IDeviceSenderCallback
 import com.android.systemui.statusbar.commandline.Command
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mock
+import org.mockito.Mockito.anyString
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 import java.io.PrintWriter
 import java.io.StringWriter
@@ -51,10 +56,19 @@
     private lateinit var mediaTttChipControllerSender: MediaTttChipControllerSender
     @Mock
     private lateinit var mediaTttChipControllerReceiver: MediaTttChipControllerReceiver
+    @Mock
+    private lateinit var mediaSenderService: IDeviceSenderCallback.Stub
+    private lateinit var mediaSenderServiceComponentName: ComponentName
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+
+        mediaSenderServiceComponentName = ComponentName(context, MediaTttSenderService::class.java)
+        context.addMockService(mediaSenderServiceComponentName, mediaSenderService)
+        whenever(mediaSenderService.queryLocalInterface(anyString())).thenReturn(mediaSenderService)
+        whenever(mediaSenderService.asBinder()).thenReturn(mediaSenderService)
+
         mediaTttCommandLineHelper =
             MediaTttCommandLineHelper(
                 commandRegistry,
@@ -102,10 +116,14 @@
     }
 
     @Test
-    fun sender_moveCloserToTransfer_chipDisplayWithCorrectState() {
-        commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
+    fun sender_moveCloserToStartCast_serviceCallbackCalled() {
+        commandRegistry.onShellCommand(pw, getMoveCloserToStartCastCommand())
 
-        verify(mediaTttChipControllerSender).displayChip(any(MoveCloserToTransfer::class.java))
+        assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue()
+
+        val deviceInfoCaptor = argumentCaptor<DeviceInfo>()
+        verify(mediaSenderService).closeToReceiverToStartCast(any(), capture(deviceInfoCaptor))
+        assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME)
     }
 
     @Test
@@ -143,11 +161,11 @@
         verify(mediaTttChipControllerReceiver).removeChip()
     }
 
-    private fun getMoveCloserToTransferCommand(): Array<String> =
+    private fun getMoveCloserToStartCastCommand(): Array<String> =
         arrayOf(
             ADD_CHIP_COMMAND_SENDER_TAG,
             DEVICE_NAME,
-            MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME
+            MOVE_CLOSER_TO_START_CAST_COMMAND_NAME
         )
 
     private fun getTransferInitiatedCommand(): Array<String> =
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 caef5b9..ecc4c46 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
@@ -66,8 +66,8 @@
     }
 
     @Test
-    fun moveCloserToTransfer_appIcon_chipTextContainsDeviceName_noLoadingIcon_noUndo() {
-        controllerSender.displayChip(moveCloserToTransfer())
+    fun moveCloserToStartCast_appIcon_chipTextContainsDeviceName_noLoadingIcon_noUndo() {
+        controllerSender.displayChip(moveCloserToStartCast())
 
         val chipView = getChipView()
         assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
@@ -192,8 +192,8 @@
     }
 
     @Test
-    fun changeFromCloserToTransferToTransferInitiated_loadingIconAppears() {
-        controllerSender.displayChip(moveCloserToTransfer())
+    fun changeFromCloserToStartToTransferInitiated_loadingIconAppears() {
+        controllerSender.displayChip(moveCloserToStartCast())
         controllerSender.displayChip(transferInitiated())
 
         assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
@@ -216,9 +216,9 @@
     }
 
     @Test
-    fun changeFromTransferSucceededToMoveCloser_undoButtonDisappears() {
+    fun changeFromTransferSucceededToMoveCloserToStart_undoButtonDisappears() {
         controllerSender.displayChip(transferSucceeded())
-        controllerSender.displayChip(moveCloserToTransfer())
+        controllerSender.displayChip(moveCloserToStartCast())
 
         assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE)
     }
@@ -240,8 +240,8 @@
     }
 
     /** Helper method providing default parameters to not clutter up the tests. */
-    private fun moveCloserToTransfer() =
-        MoveCloserToTransfer(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
+    private fun moveCloserToStartCast() =
+        MoveCloserToStartCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
 
     /** Helper method providing default parameters to not clutter up the tests. */
     private fun transferInitiated(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt
new file mode 100644
index 0000000..8f64698
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt
@@ -0,0 +1,48 @@
+package com.android.systemui.media.taptotransfer.sender
+
+import android.media.MediaRoute2Info
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.mediattt.DeviceInfo
+import com.android.systemui.shared.mediattt.IDeviceSenderCallback
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class MediaTttSenderServiceTest : SysuiTestCase() {
+
+    private lateinit var service: MediaTttSenderService
+    private lateinit var callback: IDeviceSenderCallback
+
+    @Mock
+    private lateinit var controller: MediaTttChipControllerSender
+
+    private val mediaInfo = MediaRoute2Info.Builder("id", "Test Name")
+        .addFeature("feature")
+        .build()
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        service = MediaTttSenderService(context, controller)
+        callback = IDeviceSenderCallback.Stub.asInterface(service.onBind(null))
+    }
+
+    @Test
+    fun closeToReceiverToStartCast_controllerTriggeredWithMoveCloserToStartCastState() {
+        val name = "Fake name"
+        callback.closeToReceiverToStartCast(mediaInfo, DeviceInfo(name))
+
+        val chipStateCaptor = argumentCaptor<MoveCloserToStartCast>()
+        verify(controller).displayChip(capture(chipStateCaptor))
+
+        val chipState = chipStateCaptor.value!!
+        assertThat(chipState.otherDeviceName).isEqualTo(name)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
index f56a185..2ad9c94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
@@ -130,4 +130,37 @@
         val tile = content.getChildAt(1) as QSTileView
         assertThat((tile.icon.iconView as ImageView).drawable).isNotNull()
     }
+
+    @Test
+    fun setTileData_hasNoStateDescription() {
+        val icon = Icon.createWithResource(mContext, R.drawable.cloud)
+        val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+
+        dialog.setTileData(tileData)
+        dialog.show()
+
+        TestableLooper.get(this).processAllMessages()
+
+        val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
+        val tile = content.getChildAt(1) as QSTileView
+
+        assertThat(tile.stateDescription).isEqualTo("")
+    }
+
+    @Test
+    fun setTileData_tileNotClickable() {
+        val icon = Icon.createWithResource(mContext, R.drawable.cloud)
+        val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+
+        dialog.setTileData(tileData)
+        dialog.show()
+
+        TestableLooper.get(this).processAllMessages()
+
+        val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
+        val tile = content.getChildAt(1) as QSTileView
+
+        assertThat(tile.isClickable).isFalse()
+        assertThat(tile.isLongClickable).isFalse()
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt
new file mode 100644
index 0000000..8bc438b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.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.shared.navigationbar
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.SurfaceControl
+import android.view.View
+import android.view.ViewRootImpl
+import androidx.concurrent.futures.DirectExecutor
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.*
+import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@RunWithLooper
+class RegionSamplingHelperTest : SysuiTestCase() {
+
+    @Mock
+    lateinit var sampledView: View
+    @Mock
+    lateinit var samplingCallback: RegionSamplingHelper.SamplingCallback
+    @Mock
+    lateinit var compositionListener: RegionSamplingHelper.SysuiCompositionSamplingListener
+    @Mock
+    lateinit var viewRootImpl: ViewRootImpl
+    @Mock
+    lateinit var surfaceControl: SurfaceControl
+    @Mock
+    lateinit var wrappedSurfaceControl: SurfaceControl
+    @JvmField @Rule
+    var rule = MockitoJUnit.rule()
+    lateinit var regionSamplingHelper: RegionSamplingHelper
+
+    @Before
+    fun setup() {
+        whenever(sampledView.isAttachedToWindow).thenReturn(true)
+        whenever(sampledView.viewRootImpl).thenReturn(viewRootImpl)
+        whenever(viewRootImpl.surfaceControl).thenReturn(surfaceControl)
+        whenever(surfaceControl.isValid).thenReturn(true)
+        whenever(wrappedSurfaceControl.isValid).thenReturn(true)
+        whenever(samplingCallback.isSamplingEnabled).thenReturn(true)
+        regionSamplingHelper = object : RegionSamplingHelper(sampledView, samplingCallback,
+                DirectExecutor.INSTANCE, DirectExecutor.INSTANCE, compositionListener) {
+            override fun wrap(stopLayerControl: SurfaceControl?): SurfaceControl {
+                return wrappedSurfaceControl
+            }
+        }
+        regionSamplingHelper.setWindowVisible(true)
+    }
+
+    @Test
+    fun testStart_register() {
+        regionSamplingHelper.start(Rect(0, 0, 100, 100))
+        verify(compositionListener).register(any(), anyInt(), eq(wrappedSurfaceControl), any())
+    }
+
+    @Test
+    fun testStart_unregister() {
+        regionSamplingHelper.start(Rect(0, 0, 100, 100))
+        regionSamplingHelper.setWindowVisible(false)
+        verify(compositionListener).unregister(any())
+    }
+
+    @Test
+    fun testStart_hasBlur_neverRegisters() {
+        regionSamplingHelper.setWindowHasBlurs(true)
+        regionSamplingHelper.start(Rect(0, 0, 100, 100))
+        verify(compositionListener, never())
+                .register(any(), anyInt(), eq(wrappedSurfaceControl), any())
+    }
+
+    @Test
+    fun testStart_stopAndDestroy() {
+        regionSamplingHelper.start(Rect(0, 0, 100, 100))
+        regionSamplingHelper.stopAndDestroy()
+        verify(compositionListener).unregister(any())
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
new file mode 100644
index 0000000..6971c63
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
@@ -0,0 +1,44 @@
+package com.android.systemui.statusbar
+
+import android.testing.AndroidTestingRunner
+import android.util.DisplayMetrics
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.phone.LSShadeTransitionLogger
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LSShadeTransitionLoggerTest : SysuiTestCase() {
+    lateinit var logger: LSShadeTransitionLogger
+    @Mock
+    lateinit var gestureLogger: LockscreenGestureLogger
+    @Mock
+    lateinit var displayMetrics: DisplayMetrics
+    @JvmField @Rule
+    val mockito = MockitoJUnit.rule()
+
+    @Before
+    fun setup() {
+        logger = LSShadeTransitionLogger(
+                LogBuffer("Test", 10, 10, mock()),
+                gestureLogger,
+                displayMetrics)
+    }
+
+    @Test
+    fun testLogDragDownStarted() {
+        val view: ExpandableView = mock()
+        // log a non-null, non row, ensure no crash
+        logger.logDragDownStarted(view)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index b31dd3c..eef9dd4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -23,7 +23,7 @@
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.Matchers.any;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.isA;
@@ -209,8 +209,6 @@
         when(mMockProvisionController.isCurrentUserSetup()).thenReturn(true);
         doAnswer(invocation -> {
             mUserCallback = (DeviceProvisionedListener) invocation.getArguments()[0];
-            mUserCallback.onUserSetupChanged();
-            mUserCallback.onDeviceProvisionedChanged();
             TestableLooper.get(this).processAllMessages();
             return null;
         }).when(mMockProvisionController).addCallback(any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
index 73eddd1..6262a9b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
@@ -35,6 +35,7 @@
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
 import com.android.settingslib.graph.SignalDrawable;
@@ -60,6 +61,72 @@
 public class NetworkControllerSignalTest extends NetworkControllerBaseTest {
 
     @Test
+    public void testDeviceProvisioned_userNotSetUp() {
+        // GIVEN - user is not setup
+        when(mMockProvisionController.isCurrentUserSetup()).thenReturn(false);
+
+        // WHEN - a NetworkController is created
+        mNetworkController = new NetworkControllerImpl(mContext,
+                mMockCm,
+                mMockTm,
+                mTelephonyListenerManager,
+                mMockWm,
+                mMockNsm,
+                mMockSm,
+                mConfig,
+                TestableLooper.get(this).getLooper(),
+                mFakeExecutor,
+                mCallbackHandler,
+                mock(AccessPointControllerImpl.class),
+                mock(DataUsageController.class),
+                mMockSubDefaults,
+                mMockProvisionController,
+                mMockBd,
+                mDemoModeController,
+                mCarrierConfigTracker,
+                mFeatureFlags,
+                mock(DumpManager.class)
+        );
+        TestableLooper.get(this).processAllMessages();
+
+        // THEN - NetworkController claims the user is not setup
+        assertFalse("User has not been set up", mNetworkController.isUserSetup());
+    }
+
+    @Test
+    public void testDeviceProvisioned_userSetUp() {
+        // GIVEN - user is not setup
+        when(mMockProvisionController.isCurrentUserSetup()).thenReturn(true);
+
+        // WHEN - a NetworkController is created
+        mNetworkController = new NetworkControllerImpl(mContext,
+                mMockCm,
+                mMockTm,
+                mTelephonyListenerManager,
+                mMockWm,
+                mMockNsm,
+                mMockSm,
+                mConfig,
+                TestableLooper.get(this).getLooper(),
+                mFakeExecutor,
+                mCallbackHandler,
+                mock(AccessPointControllerImpl.class),
+                mock(DataUsageController.class),
+                mMockSubDefaults,
+                mMockProvisionController,
+                mMockBd,
+                mDemoModeController,
+                mCarrierConfigTracker,
+                mFeatureFlags,
+                mock(DumpManager.class)
+        );
+        TestableLooper.get(this).processAllMessages();
+
+        // THEN - NetworkController claims the user is not setup
+        assertTrue("User has been set up", mNetworkController.isUserSetup());
+    }
+
+    @Test
     public void testNoIconWithoutMobile() {
         // Turn off mobile network support.
         when(mMockTm.isDataCapable()).thenReturn(false);
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 3047c90..6d0eadb 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -276,6 +276,9 @@
     // Package: android
     NOTE_UNBLOCK_CAM_TOGGLE = 66;
 
+    // Notify the user that a CA certificate is pending for the wifi connection.
+    NOTE_SERVER_CA_CERTIFICATE = 67;
+
     // ADD_NEW_IDS_ABOVE_THIS_LINE
     // Legacy IDs with arbitrary values appear below
     // Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index e98b63e..8e71dd3 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.companion.virtual;
 
+import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
@@ -27,9 +28,10 @@
 import android.content.pm.ActivityInfo;
 import android.os.Build;
 import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Slog;
 import android.window.DisplayWindowPolicyController;
 
-import java.util.HashSet;
 import java.util.List;
 
 
@@ -38,6 +40,8 @@
  */
 class GenericWindowPolicyController extends DisplayWindowPolicyController {
 
+    private static final String TAG = "VirtualDeviceManager";
+
     /**
      * If required, allow the secure activity to display on remote device since
      * {@link android.os.Build.VERSION_CODES#TIRAMISU}.
@@ -45,10 +49,13 @@
     @ChangeId
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public static final long ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE = 201712607L;
+    @NonNull private final ArraySet<UserHandle> mAllowedUsers;
 
-    @NonNull final HashSet<Integer> mRunningUids = new HashSet<>();
+    @NonNull final ArraySet<Integer> mRunningUids = new ArraySet<>();
 
-    GenericWindowPolicyController(int windowFlags, int systemWindowFlags) {
+    GenericWindowPolicyController(int windowFlags, int systemWindowFlags,
+            @NonNull ArraySet<UserHandle> allowedUsers) {
+        mAllowedUsers = allowedUsers;
         setInterestedWindowFlags(windowFlags, systemWindowFlags);
     }
 
@@ -58,7 +65,7 @@
         final int activityCount = activities.size();
         for (int i = 0; i < activityCount; i++) {
             final ActivityInfo aInfo = activities.get(i);
-            if ((aInfo.flags & ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
+            if (!canContainActivity(aInfo, /* windowFlags= */ 0, /* systemWindowFlags= */ 0)) {
                 return false;
             }
         }
@@ -68,21 +75,7 @@
     @Override
     public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
             int systemWindowFlags) {
-        if ((activityInfo.flags & ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
-            return false;
-        }
-        if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE,
-                activityInfo.packageName,
-                UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid))) {
-            // TODO(b/201712607): Add checks for the apps that use SurfaceView#setSecure.
-            if ((windowFlags & FLAG_SECURE) != 0) {
-                return false;
-            }
-            if ((systemWindowFlags & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) != 0) {
-                return false;
-            }
-        }
-        return true;
+        return canContainActivity(activityInfo, windowFlags, systemWindowFlags);
     }
 
     @Override
@@ -105,4 +98,28 @@
     boolean containsUid(int uid) {
         return mRunningUids.contains(uid);
     }
+
+    private boolean canContainActivity(ActivityInfo activityInfo, int windowFlags,
+            int systemWindowFlags) {
+        if ((activityInfo.flags & FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
+            return false;
+        }
+        final UserHandle activityUser =
+                UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid);
+        if (!mAllowedUsers.contains(activityUser)) {
+            Slog.d(TAG, "Virtual device activity not allowed from user " + activityUser);
+            return false;
+        }
+        if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE,
+                activityInfo.packageName, activityUser)) {
+            // TODO(b/201712607): Add checks for the apps that use SurfaceView#setSecure.
+            if ((windowFlags & FLAG_SECURE) != 0) {
+                return false;
+            }
+            if ((systemWindowFlags & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) != 0) {
+                return false;
+            }
+        }
+        return true;
+    }
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index ca35e03..1bb95f8 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -16,14 +16,20 @@
 
 package com.android.server.companion.virtual;
 
+import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_ENABLED;
+import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
+import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
 import android.annotation.NonNull;
+import android.app.admin.DevicePolicyManager;
 import android.companion.AssociationInfo;
 import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.VirtualDeviceParams;
 import android.content.Context;
 import android.graphics.Point;
+import android.hardware.display.DisplayManager;
 import android.hardware.input.VirtualKeyEvent;
 import android.hardware.input.VirtualMouseButtonEvent;
 import android.hardware.input.VirtualMouseRelativeEvent;
@@ -32,6 +38,9 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArraySet;
 import android.util.SparseArray;
 import android.window.DisplayWindowPolicyController;
 
@@ -56,6 +65,7 @@
     final List<Integer> mVirtualDisplayIds = new ArrayList<>();
     private final OnDeviceCloseListener mListener;
     private final IBinder mAppToken;
+    private final VirtualDeviceParams mParams;
 
     /**
      * A mapping from the virtual display ID to its corresponding
@@ -65,17 +75,21 @@
             new SparseArray<>();
 
     VirtualDeviceImpl(Context context, AssociationInfo associationInfo,
-            IBinder token, int ownerUid, OnDeviceCloseListener listener) {
-        this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener);
+            IBinder token, int ownerUid, OnDeviceCloseListener listener,
+            VirtualDeviceParams params) {
+        this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener,
+                params);
     }
 
     @VisibleForTesting
     VirtualDeviceImpl(Context context, AssociationInfo associationInfo, IBinder token,
-            int ownerUid, InputController inputController, OnDeviceCloseListener listener) {
+            int ownerUid, InputController inputController, OnDeviceCloseListener listener,
+            VirtualDeviceParams params) {
         mContext = context;
         mAssociationInfo = associationInfo;
         mOwnerUid = ownerUid;
         mAppToken = token;
+        mParams = params;
         if (inputController == null) {
             mInputController = new InputController(mVirtualDeviceLock);
         } else {
@@ -89,7 +103,19 @@
         }
     }
 
-    @Override
+    /**
+     * Returns the flags that should be added to any virtual displays created on this virtual
+     * device.
+     */
+    int getBaseVirtualDisplayFlags() {
+        int flags = 0;
+        if (mParams.getLockState() == VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) {
+            flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
+        }
+        return flags;
+    }
+
+    @Override // Binder call
     public int getAssociationId() {
         return mAssociationInfo.getId();
     }
@@ -267,11 +293,29 @@
         mVirtualDisplayIds.add(displayId);
         final GenericWindowPolicyController dwpc =
                 new GenericWindowPolicyController(FLAG_SECURE,
-                        SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+                        SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, getAllowedUserHandles());
         mWindowPolicyControllers.put(displayId, dwpc);
         return dwpc;
     }
 
+    private ArraySet<UserHandle> getAllowedUserHandles() {
+        ArraySet<UserHandle> result = new ArraySet<>();
+        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        UserManager userManager = mContext.getSystemService(UserManager.class);
+        for (UserHandle profile : userManager.getAllProfiles()) {
+            int nearbyAppStreamingPolicy = dpm.getNearbyAppStreamingPolicy(profile.getIdentifier());
+            if (nearbyAppStreamingPolicy == NEARBY_STREAMING_ENABLED
+                    || nearbyAppStreamingPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY) {
+                result.add(profile);
+            } else if (nearbyAppStreamingPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY) {
+                if (mParams.getUsersWithMatchingAccounts().contains(profile)) {
+                    result.add(profile);
+                }
+            }
+        }
+        return result;
+    }
+
     void onVirtualDisplayRemovedLocked(int displayId) {
         if (!mVirtualDisplayIds.contains(displayId)) {
             throw new IllegalStateException(
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 0db670e..12df79d 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -24,6 +24,7 @@
 import android.companion.CompanionDeviceManager.OnAssociationsChangedListener;
 import android.companion.virtual.IVirtualDevice;
 import android.companion.virtual.IVirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
 import android.content.Context;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -131,7 +132,10 @@
 
         @Override // Binder call
         public IVirtualDevice createVirtualDevice(
-                IBinder token, String packageName, int associationId) {
+                IBinder token,
+                String packageName,
+                int associationId,
+                @NonNull VirtualDeviceParams params) {
             getContext().enforceCallingOrSelfPermission(
                     android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
                     "createVirtualDevice");
@@ -160,7 +164,7 @@
                                     mVirtualDevices.remove(associationId);
                                 }
                             }
-                        });
+                        }, params);
                 mVirtualDevices.put(associationInfo.getId(), virtualDevice);
                 return virtualDevice;
             }
@@ -238,6 +242,11 @@
         }
 
         @Override
+        public int getBaseVirtualDisplayFlags(IVirtualDevice virtualDevice) {
+            return ((VirtualDeviceImpl) virtualDevice).getBaseVirtualDisplayFlags();
+        }
+
+        @Override
         public boolean isAppOwnerOfAnyVirtualDevice(int uid) {
             synchronized (mVirtualDeviceManagerLock) {
                 int size = mVirtualDevices.size();
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index bc8da84..37ff879 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -20,7 +20,6 @@
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
-import static android.os.UserHandle.USER_SYSTEM;
 import static android.permission.PermissionCheckerManager.PERMISSION_HARD_DENIED;
 
 import android.Manifest;
@@ -45,6 +44,7 @@
 import android.bluetooth.IBluetoothManagerCallback;
 import android.bluetooth.IBluetoothProfileServiceConnection;
 import android.bluetooth.IBluetoothStateChangeCallback;
+import android.bluetooth.IBluetoothLeCallControl;
 import android.content.ActivityNotFoundException;
 import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
@@ -64,6 +64,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IUserRestrictionsListener;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerExemptionManager;
@@ -87,9 +88,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.pm.UserManagerInternal;
-import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
-import com.android.server.pm.UserRestrictionsUtils;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -264,30 +262,34 @@
         }
     };
 
-    private final UserRestrictionsListener mUserRestrictionsListener =
-            new UserRestrictionsListener() {
+    private final IUserRestrictionsListener mUserRestrictionsListener =
+            new IUserRestrictionsListener.Stub() {
                 @Override
                 public void onUserRestrictionsChanged(int userId, Bundle newRestrictions,
                         Bundle prevRestrictions) {
 
-                    if (UserRestrictionsUtils.restrictionsChanged(prevRestrictions, newRestrictions,
-                            UserManager.DISALLOW_BLUETOOTH_SHARING)) {
+                    final boolean newDisallowBluetoothSharing = newRestrictions
+                            .getBoolean(UserManager.DISALLOW_BLUETOOTH_SHARING, false);
+                    final boolean prevDisallowBluetoothSharing = prevRestrictions
+                            .getBoolean(UserManager.DISALLOW_BLUETOOTH_SHARING, false);
+                    if (newDisallowBluetoothSharing != prevDisallowBluetoothSharing) {
                         updateOppLauncherComponentState(userId,
                                 newRestrictions.getBoolean(UserManager.DISALLOW_BLUETOOTH_SHARING));
                     }
 
+                    final boolean newDisallowBluetooth = newRestrictions
+                            .getBoolean(UserManager.DISALLOW_BLUETOOTH, false);
+                    final boolean prevDisallowBluetooth = prevRestrictions
+                            .getBoolean(UserManager.DISALLOW_BLUETOOTH, false);
                     // DISALLOW_BLUETOOTH can only be set by DO or PO on the system user.
-                    if (userId == USER_SYSTEM
-                            && UserRestrictionsUtils.restrictionsChanged(prevRestrictions,
-                            newRestrictions, UserManager.DISALLOW_BLUETOOTH)) {
-                        if (userId == USER_SYSTEM && newRestrictions.getBoolean(
-                                UserManager.DISALLOW_BLUETOOTH)) {
+                    final boolean isUserSystem = userId == UserHandle.SYSTEM.getIdentifier();
+                    if (isUserSystem && newDisallowBluetooth != prevDisallowBluetooth) {
+                        if (isUserSystem && newDisallowBluetooth) {
                             updateOppLauncherComponentState(userId, true); // Sharing disallowed
                             sendDisableMsg(BluetoothProtoEnums.ENABLE_DISABLE_REASON_DISALLOWED,
                                     mContext.getPackageName());
                         } else {
-                            updateOppLauncherComponentState(userId, newRestrictions.getBoolean(
-                                    UserManager.DISALLOW_BLUETOOTH_SHARING));
+                            updateOppLauncherComponentState(userId, newDisallowBluetoothSharing);
                         }
                     }
                 }
@@ -540,7 +542,7 @@
         if (!noHome) {
             PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
             systemUiUid = pm.getPackageUid(pm.getSystemUiServiceComponent().getPackageName(),
-                    MATCH_SYSTEM_ONLY, USER_SYSTEM);
+                    MATCH_SYSTEM_ONLY, UserHandle.SYSTEM.getIdentifier());
         }
         if (systemUiUid >= 0) {
             Slog.d(TAG, "Detected SystemUiUid: " + Integer.toString(systemUiUid));
@@ -1323,11 +1325,15 @@
                             + bluetoothProfile);
                 }
 
-                if (bluetoothProfile != BluetoothProfile.HEADSET) {
+                Intent intent;
+                if (bluetoothProfile == BluetoothProfile.HEADSET) {
+                    intent = new Intent(IBluetoothHeadset.class.getName());
+                } else if (bluetoothProfile== BluetoothProfile.LE_CALL_CONTROL) {
+                    intent = new Intent(IBluetoothLeCallControl.class.getName());
+                } else {
                     return false;
                 }
 
-                Intent intent = new Intent(IBluetoothHeadset.class.getName());
                 psc = new ProfileServiceConnections(intent);
                 if (!psc.bindService()) {
                     return false;
@@ -1391,9 +1397,8 @@
             Slog.d(TAG, "Bluetooth boot completed");
         }
         mAppOps = mContext.getSystemService(AppOpsManager.class);
-        UserManagerInternal userManagerInternal =
-                LocalServices.getService(UserManagerInternal.class);
-        userManagerInternal.addUserRestrictionsListener(mUserRestrictionsListener);
+        UserManager userManager = mContext.getSystemService(UserManager.class);
+        userManager.addUserRestrictionsListener(mUserRestrictionsListener);
         final boolean isBluetoothDisallowed = isBluetoothDisallowed();
         if (isBluetoothDisallowed) {
             return;
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index a2c2dbd..3951680 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -39,6 +39,8 @@
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.TrafficStats.UID_TETHERING;
 
+import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT;
+
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.content.Context;
@@ -133,12 +135,6 @@
 
     private static final int MAX_UID_RANGES_PER_COMMAND = 10;
 
-    /**
-     * Name representing {@link #setGlobalAlert(long)} limit when delivered to
-     * {@link INetworkManagementEventObserver#limitReached(String, String)}.
-     */
-    public static final String LIMIT_GLOBAL_ALERT = "globalAlert";
-
     static final int DAEMON_MSG_MOBILE_CONN_REAL_TIME_INFO = 1;
 
     static final boolean MODIFY_OPERATION_ADD = true;
diff --git a/services/core/java/com/android/server/SmartStorageMaintIdler.java b/services/core/java/com/android/server/SmartStorageMaintIdler.java
new file mode 100644
index 0000000..2dff72f
--- /dev/null
+++ b/services/core/java/com/android/server/SmartStorageMaintIdler.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+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.util.Slog;
+
+import java.util.concurrent.TimeUnit;
+
+public class SmartStorageMaintIdler extends JobService {
+    private static final String TAG = "SmartStorageMaintIdler";
+
+    private static final ComponentName SMART_STORAGE_MAINT_SERVICE =
+            new ComponentName("android", SmartStorageMaintIdler.class.getName());
+
+    private static final int SMART_MAINT_JOB_ID = 2808;
+
+    private boolean mStarted;
+    private JobParameters mJobParams;
+    private final Runnable mFinishCallback = new Runnable() {
+        @Override
+        public void run() {
+            Slog.i(TAG, "Got smart storage maintenance service completion callback");
+            if (mStarted) {
+                jobFinished(mJobParams, false);
+                mStarted = false;
+            }
+            // ... and try again in a next period
+            scheduleSmartIdlePass(SmartStorageMaintIdler.this,
+                StorageManagerService.SMART_IDLE_MAINT_PERIOD);
+        }
+    };
+
+    @Override
+    public boolean onStartJob(JobParameters params) {
+        mJobParams = params;
+        StorageManagerService ms = StorageManagerService.sSelf;
+        if (ms != null) {
+            mStarted = true;
+            ms.runSmartIdleMaint(mFinishCallback);
+        }
+        return ms != null;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        mStarted = false;
+        return false;
+    }
+
+    /**
+     * Schedule the smart storage idle maintenance job
+     */
+    public static void scheduleSmartIdlePass(Context context, int nHours) {
+        StorageManagerService ms = StorageManagerService.sSelf;
+        if ((ms == null) || ms.isPassedLifetimeThresh()) {
+            return;
+        }
+
+        JobScheduler tm = context.getSystemService(JobScheduler.class);
+
+        long nextScheduleTime = TimeUnit.HOURS.toMillis(nHours);
+
+        JobInfo.Builder builder = new JobInfo.Builder(SMART_MAINT_JOB_ID,
+            SMART_STORAGE_MAINT_SERVICE);
+
+        builder.setMinimumLatency(nextScheduleTime);
+        tm.schedule(builder.build());
+    }
+}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index ec018de..98764e0 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -79,6 +79,7 @@
 import android.content.res.ObbInfo;
 import android.database.ContentObserver;
 import android.net.Uri;
+import android.os.BatteryManager;
 import android.os.Binder;
 import android.os.Build;
 import android.os.DropBoxManager;
@@ -159,6 +160,8 @@
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal.ScreenObserver;
 
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import libcore.io.IoUtils;
 import libcore.util.EmptyArray;
 
@@ -340,7 +343,44 @@
 
     @Nullable public static String sMediaStoreAuthorityProcessName;
 
+    // Run period in hour for smart idle maintenance
+    static final int SMART_IDLE_MAINT_PERIOD = 1;
+
     private final AtomicFile mSettingsFile;
+    private final AtomicFile mHourlyWriteFile;
+
+    private static final int MAX_HOURLY_WRITE_RECORDS = 72;
+
+    /**
+     * Default config values for smart idle maintenance
+     * Actual values will be controlled by DeviceConfig
+     */
+    // Decide whether smart idle maintenance is enabled or not
+    private static final boolean DEFAULT_SMART_IDLE_MAINT_ENABLED = false;
+    // Storage lifetime percentage threshold to decide to turn off the feature
+    private static final int DEFAULT_LIFETIME_PERCENT_THRESHOLD = 70;
+    // Minimum required number of dirty + free segments to trigger GC
+    private static final int DEFAULT_MIN_SEGMENTS_THRESHOLD = 512;
+    // Determine how much portion of current dirty segments will be GCed
+    private static final float DEFAULT_DIRTY_RECLAIM_RATE = 0.5F;
+    // Multiplier to amplify the target segment number for GC
+    private static final float DEFAULT_SEGMENT_RECLAIM_WEIGHT = 1.0F;
+    // Low battery level threshold to decide to turn off the feature
+    private static final float DEFAULT_LOW_BATTERY_LEVEL = 20F;
+    // Decide whether charging is required to turn on the feature
+    private static final boolean DEFAULT_CHARGING_REQUIRED = true;
+
+    private volatile int mLifetimePercentThreshold;
+    private volatile int mMinSegmentsThreshold;
+    private volatile float mDirtyReclaimRate;
+    private volatile float mSegmentReclaimWeight;
+    private volatile float mLowBatteryLevel;
+    private volatile boolean mChargingRequired;
+    private volatile boolean mNeedGC;
+
+    private volatile boolean mPassedLifetimeThresh;
+    // Tracking storage hourly write amounts
+    private volatile int[] mStorageHourlyWrites;
 
     /**
      * <em>Never</em> hold the lock while performing downcalls into vold, since
@@ -896,6 +936,10 @@
     }
 
     private void handleSystemReady() {
+        if (prepareSmartIdleMaint()) {
+            SmartStorageMaintIdler.scheduleSmartIdlePass(mContext, SMART_IDLE_MAINT_PERIOD);
+        }
+
         // Start scheduling nominally-daily fstrim operations
         MountServiceIdler.scheduleIdlePass(mContext);
 
@@ -1912,6 +1956,10 @@
 
         mSettingsFile = new AtomicFile(
                 new File(Environment.getDataSystemDirectory(), "storage.xml"), "storage-settings");
+        mHourlyWriteFile = new AtomicFile(
+                new File(Environment.getDataSystemDirectory(), "storage-hourly-writes"));
+
+        mStorageHourlyWrites = new int[MAX_HOURLY_WRITE_RECORDS];
 
         synchronized (mLock) {
             readSettingsLocked();
@@ -2574,7 +2622,7 @@
             // fstrim time is still updated. If file based checkpoints are used, we run
             // idle maintenance (GC + fstrim) regardless of checkpoint status.
             if (!needsCheckpoint() || !supportsBlockCheckpoint()) {
-                mVold.runIdleMaint(new IVoldTaskListener.Stub() {
+                mVold.runIdleMaint(mNeedGC, new IVoldTaskListener.Stub() {
                     @Override
                     public void onStatus(int status, PersistableBundle extras) {
                         // Not currently used
@@ -2625,6 +2673,176 @@
         abortIdleMaint(null);
     }
 
+    private boolean prepareSmartIdleMaint() {
+        /**
+         * We can choose whether going with a new storage smart idle maintenance job
+         * or falling back to the traditional way using DeviceConfig
+         */
+        boolean smartIdleMaintEnabled = DeviceConfig.getBoolean(
+            DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+            "smart_idle_maint_enabled",
+            DEFAULT_SMART_IDLE_MAINT_ENABLED);
+        if (smartIdleMaintEnabled) {
+            mLifetimePercentThreshold = DeviceConfig.getInt(
+                DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+                "lifetime_threshold", DEFAULT_LIFETIME_PERCENT_THRESHOLD);
+            mMinSegmentsThreshold = DeviceConfig.getInt(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+                "min_segments_threshold", DEFAULT_MIN_SEGMENTS_THRESHOLD);
+            mDirtyReclaimRate = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+                "dirty_reclaim_rate", DEFAULT_DIRTY_RECLAIM_RATE);
+            mSegmentReclaimWeight = DeviceConfig.getFloat(
+                DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+                "segment_reclaim_weight", DEFAULT_SEGMENT_RECLAIM_WEIGHT);
+            mLowBatteryLevel = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+                "low_battery_level", DEFAULT_LOW_BATTERY_LEVEL);
+            mChargingRequired = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+                "charging_required", DEFAULT_CHARGING_REQUIRED);
+
+            // If we use the smart idle maintenance, we need to turn off GC in the traditional idle
+            // maintenance to avoid the conflict
+            mNeedGC = false;
+
+            loadStorageHourlyWrites();
+            try {
+                mVold.refreshLatestWrite();
+            } catch (Exception e) {
+                Slog.wtf(TAG, e);
+            }
+            refreshLifetimeConstraint();
+        }
+        return smartIdleMaintEnabled;
+    }
+
+    // Return whether storage lifetime exceeds the threshold
+    public boolean isPassedLifetimeThresh() {
+        return mPassedLifetimeThresh;
+    }
+
+    private void loadStorageHourlyWrites() {
+        FileInputStream fis = null;
+
+        try {
+            fis = mHourlyWriteFile.openRead();
+            ObjectInputStream ois = new ObjectInputStream(fis);
+            mStorageHourlyWrites = (int[])ois.readObject();
+        } catch (FileNotFoundException e) {
+            // Missing data is okay, probably first boot
+        } catch (Exception e) {
+            Slog.wtf(TAG, "Failed reading write records", e);
+        } finally {
+            IoUtils.closeQuietly(fis);
+        }
+    }
+
+    private int getAverageHourlyWrite() {
+        return Arrays.stream(mStorageHourlyWrites).sum() / MAX_HOURLY_WRITE_RECORDS;
+    }
+
+    private void updateStorageHourlyWrites(int latestWrite) {
+        FileOutputStream fos = null;
+
+        System.arraycopy(mStorageHourlyWrites,0, mStorageHourlyWrites, 1,
+                     MAX_HOURLY_WRITE_RECORDS - 1);
+        mStorageHourlyWrites[0] = latestWrite;
+        try {
+            fos = mHourlyWriteFile.startWrite();
+            ObjectOutputStream oos = new ObjectOutputStream(fos);
+            oos.writeObject(mStorageHourlyWrites);
+            mHourlyWriteFile.finishWrite(fos);
+        } catch (IOException e) {
+            if (fos != null) {
+                mHourlyWriteFile.failWrite(fos);
+            }
+        }
+    }
+
+    private boolean checkChargeStatus() {
+        IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+        Intent batteryStatus = mContext.registerReceiver(null, ifilter);
+
+        if (mChargingRequired) {
+            int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
+            if (status != BatteryManager.BATTERY_STATUS_CHARGING &&
+                status != BatteryManager.BATTERY_STATUS_FULL) {
+                Slog.w(TAG, "Battery is not being charged");
+                return false;
+            }
+        }
+
+        int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+        int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
+        float chargePercent = level * 100f / (float)scale;
+
+        if (chargePercent < mLowBatteryLevel) {
+            Slog.w(TAG, "Battery level is " + chargePercent + ", which is lower than threshold: " +
+                        mLowBatteryLevel);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean refreshLifetimeConstraint() {
+        int storageLifeTime = 0;
+
+        try {
+            storageLifeTime = mVold.getStorageLifeTime();
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
+            return false;
+        }
+
+        if (storageLifeTime == -1) {
+            Slog.w(TAG, "Failed to get storage lifetime");
+            return false;
+        } else if (storageLifeTime > mLifetimePercentThreshold) {
+            Slog.w(TAG, "Ended smart idle maintenance, because of lifetime(" + storageLifeTime +
+                        ")" + ", lifetime threshold(" + mLifetimePercentThreshold + ")");
+            mPassedLifetimeThresh = true;
+            return false;
+        }
+        Slog.i(TAG, "Storage lifetime: " + storageLifeTime);
+        return true;
+    }
+
+    void runSmartIdleMaint(Runnable callback) {
+        enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+
+        try {
+            // Block based checkpoint process runs fstrim. So, if checkpoint is in progress
+            // (first boot after OTA), We skip the smart idle maintenance
+            if (!needsCheckpoint() || !supportsBlockCheckpoint()) {
+                if (!refreshLifetimeConstraint() || !checkChargeStatus()) {
+                    return;
+                }
+
+                int latestHourlyWrite = mVold.getWriteAmount();
+                if (latestHourlyWrite == -1) {
+                    Slog.w(TAG, "Failed to get storage hourly write");
+                    return;
+                }
+
+                updateStorageHourlyWrites(latestHourlyWrite);
+                int avgHourlyWrite = getAverageHourlyWrite();
+
+                Slog.i(TAG, "Set smart idle maintenance: " + "latest hourly write: " +
+                            latestHourlyWrite + ", average hourly write: " + avgHourlyWrite +
+                            ", min segment threshold: " + mMinSegmentsThreshold +
+                            ", dirty reclaim rate: " + mDirtyReclaimRate +
+                            ", segment reclaim weight:" + mSegmentReclaimWeight);
+                mVold.setGCUrgentPace(avgHourlyWrite, mMinSegmentsThreshold, mDirtyReclaimRate,
+                                      mSegmentReclaimWeight);
+            } else {
+                Slog.i(TAG, "Skipping smart idle maintenance - block based checkpoint in progress");
+            }
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
+        } finally {
+            if (callback != null) {
+                callback.run();
+            }
+        }
+    }
+
     @Override
     public void setDebugFlags(int flags, int mask) {
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 5f295ac..e7f4de2 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -26,6 +26,7 @@
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.util.ArraySet;
 import android.util.EventLog;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
@@ -47,6 +48,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
@@ -94,6 +96,7 @@
 
     // Services that should receive lifecycle events.
     private List<SystemService> mServices;
+    private Set<String> mServiceClassnames;
 
     private int mCurrentPhase = -1;
 
@@ -116,6 +119,7 @@
     SystemServiceManager(Context context) {
         mContext = context;
         mServices = new ArrayList<>();
+        mServiceClassnames = new ArraySet<>();
         // Disable using the thread pool for low ram devices
         sUseLifecycleThreadPool = sUseLifecycleThreadPool
                 && !ActivityManager.isLowRamDeviceStatic();
@@ -210,8 +214,17 @@
     }
 
     public void startService(@NonNull final SystemService service) {
+        // Check if already started
+        String className = service.getClass().getName();
+        if (mServiceClassnames.contains(className)) {
+            Slog.i(TAG, "Not starting an already started service " + className);
+            return;
+        }
+        mServiceClassnames.add(className);
+
         // Register it.
         mServices.add(service);
+
         // Start it.
         long time = SystemClock.elapsedRealtime();
         try {
@@ -225,6 +238,7 @@
 
     /** Disallow starting new services after this call. */
     void sealStartedServices() {
+        mServiceClassnames = Collections.emptySet();
         mServices = Collections.unmodifiableList(mServices);
     }
 
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index f591b26..297d28d 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.util.dump.DumpUtils.writeStringIfNotNull;
 
+import android.annotation.NonNull;
 import android.annotation.TestApi;
 import android.app.ActivityManager;
 import android.app.Notification;
@@ -170,6 +171,12 @@
         mAdbConnectionInfo = new AdbConnectionInfo();
     }
 
+    static void sendBroadcastWithDebugPermission(@NonNull Context context, @NonNull Intent intent,
+            @NonNull UserHandle userHandle) {
+        context.sendBroadcastAsUser(intent, userHandle,
+                android.Manifest.permission.MANAGE_DEBUGGING);
+    }
+
     class PairingThread extends Thread implements NsdManager.RegistrationListener {
         private NsdManager mNsdManager;
         private String mPublicKey;
@@ -1278,7 +1285,7 @@
                     ? AdbManager.WIRELESS_STATUS_CONNECTED
                     : AdbManager.WIRELESS_STATUS_DISCONNECTED);
             intent.putExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, port);
-            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+            AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent, UserHandle.ALL);
         }
 
         private void onAdbdWifiServerConnected(int port) {
@@ -1350,7 +1357,8 @@
             if (publicKey == null) {
                 Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION);
                 intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA, AdbManager.WIRELESS_STATUS_FAIL);
-                mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+                AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent,
+                        UserHandle.ALL);
             } else {
                 Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION);
                 intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA,
@@ -1366,7 +1374,8 @@
                 device.guid = hostname;
                 device.connected = false;
                 intent.putExtra(AdbManager.WIRELESS_PAIR_DEVICE_EXTRA, device);
-                mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+                AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent,
+                        UserHandle.ALL);
                 // Add the key into the keystore
                 mAdbKeyStore.setLastConnectionTime(publicKey,
                         System.currentTimeMillis());
@@ -1380,14 +1389,14 @@
             intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA,
                     AdbManager.WIRELESS_STATUS_CONNECTED);
             intent.putExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, port);
-            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+            AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent, UserHandle.ALL);
         }
 
         private void sendPairedDevicesToUI(Map<String, PairDevice> devices) {
             Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_PAIRED_DEVICES_ACTION);
             // Map is not serializable, so need to downcast
             intent.putExtra(AdbManager.WIRELESS_DEVICES_EXTRA, (HashMap) devices);
-            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+            AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent, UserHandle.ALL);
         }
 
         private void updateUIPairCode(String code) {
@@ -1397,7 +1406,7 @@
             intent.putExtra(AdbManager.WIRELESS_PAIRING_CODE_EXTRA, code);
             intent.putExtra(AdbManager.WIRELESS_STATUS_EXTRA,
                     AdbManager.WIRELESS_STATUS_PAIRING_CODE);
-            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+            AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent, UserHandle.ALL);
         }
     }
 
diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java
index 7a4d2ce..2845fbf 100644
--- a/services/core/java/com/android/server/adb/AdbService.java
+++ b/services/core/java/com/android/server/adb/AdbService.java
@@ -459,7 +459,7 @@
                 ? AdbManager.WIRELESS_STATUS_CONNECTED
                 : AdbManager.WIRELESS_STATUS_DISCONNECTED);
         intent.putExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, port);
-        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent, UserHandle.ALL);
         Slog.i(TAG, "sent port broadcast port=" + port);
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 704d739..70bd734 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -13378,7 +13378,12 @@
                                         !intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false);
                                 final boolean fullUninstall = removed && !replacing;
                                 if (removed) {
-                                    if (!killProcess) {
+                                    if (killProcess) {
+                                        forceStopPackageLocked(ssp, UserHandle.getAppId(
+                                                intent.getIntExtra(Intent.EXTRA_UID, -1)),
+                                                false, true, true, false, fullUninstall, userId,
+                                                removed ? "pkg removed" : "pkg changed");
+                                    } else {
                                         // Kill any app zygotes always, since they can't fork new
                                         // processes with references to the old code
                                         forceStopAppZygoteLocked(ssp, UserHandle.getAppId(
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index 336572f..8561b61 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -16,6 +16,7 @@
 package com.android.server.am;
 
 import android.annotation.Nullable;
+import android.app.usage.NetworkStatsManager;
 import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
@@ -712,8 +713,10 @@
             if (wifiInfo.isValid()) {
                 final long wifiChargeUC = measuredEnergyDeltas != null ?
                         measuredEnergyDeltas.wifiChargeUC : MeasuredEnergySnapshot.UNAVAILABLE;
-                mStats.updateWifiState(
-                        extractDeltaLocked(wifiInfo), wifiChargeUC, elapsedRealtime, uptime);
+                final NetworkStatsManager networkStatsManager = mInjector.getSystemService(
+                        NetworkStatsManager.class);
+                mStats.updateWifiState(extractDeltaLocked(wifiInfo),
+                        wifiChargeUC, elapsedRealtime, uptime, networkStatsManager);
             } else {
                 Slog.w(TAG, "wifi info is invalid: " + wifiInfo);
             }
@@ -722,8 +725,10 @@
         if (modemInfo != null) {
             final long mobileRadioChargeUC = measuredEnergyDeltas != null
                     ? measuredEnergyDeltas.mobileRadioChargeUC : MeasuredEnergySnapshot.UNAVAILABLE;
+            final NetworkStatsManager networkStatsManager = mInjector.getSystemService(
+                    NetworkStatsManager.class);
             mStats.noteModemControllerActivity(modemInfo, mobileRadioChargeUC, elapsedRealtime,
-                    uptime);
+                    uptime, networkStatsManager);
         }
 
         if (updateFlags == UPDATE_ALL) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 8cb2040..9ffafe25 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -22,6 +22,7 @@
 
 import android.annotation.NonNull;
 import android.app.StatsManager;
+import android.app.usage.NetworkStatsManager;
 import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -2029,8 +2030,11 @@
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
+            final NetworkStatsManager networkStatsManager = mContext.getSystemService(
+                    NetworkStatsManager.class);
             mHandler.post(() -> {
-                mStats.updateWifiState(info, POWER_DATA_UNAVAILABLE, elapsedRealtime, uptime);
+                mStats.updateWifiState(info, POWER_DATA_UNAVAILABLE, elapsedRealtime, uptime,
+                        networkStatsManager);
             });
         }
     }
@@ -2067,9 +2071,11 @@
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
+            final NetworkStatsManager networkStatsManager = mContext.getSystemService(
+                    NetworkStatsManager.class);
             mHandler.post(() -> {
                 mStats.noteModemControllerActivity(info, POWER_DATA_UNAVAILABLE, elapsedRealtime,
-                        uptime);
+                        uptime, networkStatsManager);
             });
         }
     }
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index c55bbe8..c08cf64 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -539,6 +539,8 @@
      */
     static private native void compactProcess(int pid, int compactionFlags);
 
+    static private native void cancelCompaction();
+
     /**
      * Reads the flag value from DeviceConfig to determine whether app compaction
      * should be enabled, and starts the freeze/compaction thread if needed.
@@ -1049,6 +1051,26 @@
         }
     }
 
+    @GuardedBy({"mService", "mProcLock"})
+    void onOomAdjustChanged(int oldAdj, int newAdj, ProcessRecord app) {
+        // Cancel any currently executing compactions
+        // if the process moved out of cached state
+        if (DefaultProcessDependencies.mPidCompacting == app.mPid && newAdj < oldAdj
+                && newAdj < ProcessList.CACHED_APP_MIN_ADJ) {
+            cancelCompaction();
+        }
+
+        // Perform a minor compaction when a perceptible app becomes the prev/home app
+        // Perform a major compaction when any app enters cached
+        if (oldAdj <= ProcessList.PERCEPTIBLE_APP_ADJ
+                && (newAdj == ProcessList.PREVIOUS_APP_ADJ || newAdj == ProcessList.HOME_APP_ADJ)) {
+            compactAppSome(app);
+        } else if (newAdj >= ProcessList.CACHED_APP_MIN_ADJ
+                && newAdj <= ProcessList.CACHED_APP_MAX_ADJ) {
+            compactAppFull(app);
+        }
+    }
+
     @VisibleForTesting
     static final class LastCompactionStats {
         private final long[] mRssAfterCompaction;
@@ -1091,6 +1113,13 @@
                         name = proc.processName;
                         opt.setHasPendingCompact(false);
 
+                        if (mAm.mInternal.isPendingTopUid(proc.uid)) {
+                            // In case the OOM Adjust has not yet been propagated we see if this is
+                            // pending on becoming top app in which case we should not compact.
+                            Slog.e(TAG_AM, "Skip compaction since UID is active for  " + name);
+                            return;
+                        }
+
                         // don't compact if the process has returned to perceptible
                         // and this is only a cached/home/prev compaction
                         if ((pendingAction == COMPACT_PROCESS_SOME
@@ -1500,6 +1529,8 @@
      * Default implementation for ProcessDependencies, public vor visibility to OomAdjuster class.
      */
     private static final class DefaultProcessDependencies implements ProcessDependencies {
+        public static int mPidCompacting = -1;
+
         // Get memory RSS from process.
         @Override
         public long[] getRss(int pid) {
@@ -1509,6 +1540,7 @@
         // Compact process.
         @Override
         public void performCompaction(String action, int pid) throws IOException {
+            mPidCompacting = pid;
             if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_FULL])) {
                 compactProcess(pid, COMPACT_ACTION_FILE_FLAG | COMPACT_ACTION_ANON_FLAG);
             } else if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_FILE])) {
@@ -1516,6 +1548,7 @@
             } else if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_ANON])) {
                 compactProcess(pid, COMPACT_ACTION_ANON_FLAG);
             }
+            mPidCompacting = -1;
         }
     }
 }
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index af4ff58..b123496 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2486,18 +2486,9 @@
         // don't compact during bootup
         if (mCachedAppOptimizer.useCompaction() && mService.mBooted) {
             // Cached and prev/home compaction
+            // reminder: here, setAdj is previous state, curAdj is upcoming state
             if (state.getCurAdj() != state.getSetAdj()) {
-                // Perform a minor compaction when a perceptible app becomes the prev/home app
-                // Perform a major compaction when any app enters cached
-                // reminder: here, setAdj is previous state, curAdj is upcoming state
-                if (state.getSetAdj() <= ProcessList.PERCEPTIBLE_APP_ADJ
-                        && (state.getCurAdj() == ProcessList.PREVIOUS_APP_ADJ
-                            || state.getCurAdj() == ProcessList.HOME_APP_ADJ)) {
-                    mCachedAppOptimizer.compactAppSome(app);
-                } else if (state.getCurAdj() >= ProcessList.CACHED_APP_MIN_ADJ
-                        && state.getCurAdj() <= ProcessList.CACHED_APP_MAX_ADJ) {
-                    mCachedAppOptimizer.compactAppFull(app);
-                }
+                mCachedAppOptimizer.onOomAdjustChanged(state.getSetAdj(), state.getCurAdj(), app);
             } else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE
                     && state.getSetAdj() < ProcessList.FOREGROUND_APP_ADJ
                     // Because these can fire independent of oom_adj/procstate changes, we need
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 9b731d5..b3e46cd 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -174,6 +174,9 @@
     boolean mFgsNotificationWasDeferred;
     // FGS notification was shown before the FGS finishes, or it wasn't deferred in the first place.
     boolean mFgsNotificationShown;
+    // Whether FGS package has permissions to show notifications.
+    // TODO(b/194833441): Output this field to logs in ActiveServices#logFGSStateChangeLocked.
+    boolean mFgsHasNotificationPermission;
 
     // allow the service becomes foreground service? Service started from background may not be
     // allowed to become a foreground service.
@@ -968,6 +971,9 @@
                     if (nm == null) {
                         return;
                     }
+                    // Record whether the package has permission to notify the user
+                    mFgsHasNotificationPermission = nm.areNotificationsEnabledForPackage(
+                            localPackageName, appUid);
                     Notification localForegroundNoti = _foregroundNoti;
                     try {
                         if (localForegroundNoti.getSmallIcon() == null) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 69765d2..805e45d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -8541,7 +8541,7 @@
 
     /** @see Spatializer#getSpatializerCompatibleAudioDevices() */
     public @NonNull List<AudioDeviceAttributes> getSpatializerCompatibleAudioDevices() {
-        enforceModifyAudioRoutingPermission();
+        enforceModifyDefaultAudioEffectsPermission();
         return mSpatializerHelper.getCompatibleAudioDevices();
     }
 
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 9273a5d..6ec9836 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -102,8 +102,6 @@
     /*package*/  static final int SCO_MODE_UNDEFINED = -1;
     // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall())
     /*package*/  static final int SCO_MODE_VIRTUAL_CALL = 0;
-    // SCO audio mode is raw audio (BluetoothHeadset.connectAudio())
-    private  static final int SCO_MODE_RAW = 1;
     // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition())
     private  static final int SCO_MODE_VR = 2;
     // max valid SCO audio mode values
@@ -122,8 +120,6 @@
                 return "SCO_MODE_UNDEFINED";
             case SCO_MODE_VIRTUAL_CALL:
                 return "SCO_MODE_VIRTUAL_CALL";
-            case SCO_MODE_RAW:
-                return "SCO_MODE_RAW";
             case SCO_MODE_VR:
                 return "SCO_MODE_VR";
             default:
@@ -812,8 +808,6 @@
     private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
             BluetoothDevice device, int scoAudioMode) {
         switch (scoAudioMode) {
-            case SCO_MODE_RAW:
-                return bluetoothHeadset.disconnectAudio();
             case SCO_MODE_VIRTUAL_CALL:
                 return bluetoothHeadset.stopScoUsingVirtualVoiceCall();
             case SCO_MODE_VR:
@@ -826,8 +820,6 @@
     private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
             BluetoothDevice device, int scoAudioMode) {
         switch (scoAudioMode) {
-            case SCO_MODE_RAW:
-                return bluetoothHeadset.connectAudio();
             case SCO_MODE_VIRTUAL_CALL:
                 return bluetoothHeadset.startScoUsingVirtualVoiceCall();
             case SCO_MODE_VR:
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index 26bbb40..b73e911 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -16,8 +16,6 @@
 
 package com.android.server.biometrics.sensors;
 
-import static com.android.internal.annotations.VisibleForTesting.Visibility;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -50,6 +48,7 @@
      * Interface that ClientMonitor holders should use to receive callbacks.
      */
     public interface Callback {
+
         /**
          * Invoked when the ClientMonitor operation has been started (e.g. reached the head of
          * the queue and becomes the current operation).
@@ -204,8 +203,7 @@
     }
 
     /** Signals this operation has completed its lifecycle and should no longer be used. */
-    @VisibleForTesting(visibility = Visibility.PACKAGE)
-    public void destroy() {
+    void destroy() {
         mAlreadyDone = true;
         if (mToken != null) {
             try {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 1f91c4d..a358bc2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -17,14 +17,15 @@
 package com.android.server.biometrics.sensors;
 
 import android.annotation.IntDef;
-import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Slog;
@@ -54,7 +55,6 @@
  * We currently assume (and require) that each biometric sensor have its own instance of a
  * {@link BiometricScheduler}. See {@link CoexCoordinator}.
  */
-@MainThread
 public class BiometricScheduler {
 
     private static final String BASE_TAG = "BiometricScheduler";
@@ -110,6 +110,123 @@
         }
     }
 
+    /**
+     * Contains all the necessary information for a HAL operation.
+     */
+    @VisibleForTesting
+    static final class Operation {
+
+        /**
+         * The operation is added to the list of pending operations and waiting for its turn.
+         */
+        static final int STATE_WAITING_IN_QUEUE = 0;
+
+        /**
+         * The operation is added to the list of pending operations, but a subsequent operation
+         * has been added. This state only applies to {@link Interruptable} operations. When this
+         * operation reaches the head of the queue, it will send ERROR_CANCELED and finish.
+         */
+        static final int STATE_WAITING_IN_QUEUE_CANCELING = 1;
+
+        /**
+         * The operation has reached the front of the queue and has started.
+         */
+        static final int STATE_STARTED = 2;
+
+        /**
+         * The operation was started, but is now canceling. Operations should wait for the HAL to
+         * acknowledge that the operation was canceled, at which point it finishes.
+         */
+        static final int STATE_STARTED_CANCELING = 3;
+
+        /**
+         * The operation has reached the head of the queue but is waiting for BiometricService
+         * to acknowledge and start the operation.
+         */
+        static final int STATE_WAITING_FOR_COOKIE = 4;
+
+        /**
+         * The {@link BaseClientMonitor.Callback} has been invoked and the client is finished.
+         */
+        static final int STATE_FINISHED = 5;
+
+        @IntDef({STATE_WAITING_IN_QUEUE,
+                STATE_WAITING_IN_QUEUE_CANCELING,
+                STATE_STARTED,
+                STATE_STARTED_CANCELING,
+                STATE_WAITING_FOR_COOKIE,
+                STATE_FINISHED})
+        @Retention(RetentionPolicy.SOURCE)
+        @interface OperationState {}
+
+        @NonNull final BaseClientMonitor mClientMonitor;
+        @Nullable final BaseClientMonitor.Callback mClientCallback;
+        @OperationState int mState;
+
+        Operation(
+                @NonNull BaseClientMonitor clientMonitor,
+                @Nullable BaseClientMonitor.Callback callback
+        ) {
+            this(clientMonitor, callback, STATE_WAITING_IN_QUEUE);
+        }
+
+        protected Operation(
+                @NonNull BaseClientMonitor clientMonitor,
+                @Nullable BaseClientMonitor.Callback callback,
+                @OperationState int state
+        ) {
+            mClientMonitor = clientMonitor;
+            mClientCallback = callback;
+            mState = state;
+        }
+
+        public boolean isHalOperation() {
+            return mClientMonitor instanceof HalClientMonitor<?>;
+        }
+
+        /**
+         * @return true if the operation requires the HAL, and the HAL is null.
+         */
+        public boolean isUnstartableHalOperation() {
+            if (isHalOperation()) {
+                final HalClientMonitor<?> client = (HalClientMonitor<?>) mClientMonitor;
+                if (client.getFreshDaemon() == null) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public String toString() {
+            return mClientMonitor + ", State: " + mState;
+        }
+    }
+
+    /**
+     * Monitors an operation's cancellation. If cancellation takes too long, the watchdog will
+     * kill the current operation and forcibly start the next.
+     */
+    private static final class CancellationWatchdog implements Runnable {
+        static final int DELAY_MS = 3000;
+
+        final String tag;
+        final Operation operation;
+        CancellationWatchdog(String tag, Operation operation) {
+            this.tag = tag;
+            this.operation = operation;
+        }
+
+        @Override
+        public void run() {
+            if (operation.mState != Operation.STATE_FINISHED) {
+                Slog.e(tag, "[Watchdog Triggered]: " + operation);
+                operation.mClientMonitor.mCallback
+                        .onClientFinished(operation.mClientMonitor, false /* success */);
+            }
+        }
+    }
+
     private static final class CrashState {
         static final int NUM_ENTRIES = 10;
         final String timestamp;
@@ -146,9 +263,10 @@
     private final @SensorType int mSensorType;
     @Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
     @NonNull private final IBiometricService mBiometricService;
-    @NonNull protected final Handler mHandler;
-    @VisibleForTesting @NonNull final Deque<BiometricSchedulerOperation> mPendingOperations;
-    @VisibleForTesting @Nullable BiometricSchedulerOperation mCurrentOperation;
+    @NonNull protected final Handler mHandler = new Handler(Looper.getMainLooper());
+    @NonNull private final InternalCallback mInternalCallback;
+    @VisibleForTesting @NonNull final Deque<Operation> mPendingOperations;
+    @VisibleForTesting @Nullable Operation mCurrentOperation;
     @NonNull private final ArrayDeque<CrashState> mCrashStates;
 
     private int mTotalOperationsHandled;
@@ -159,7 +277,7 @@
     // Internal callback, notified when an operation is complete. Notifies the requester
     // that the operation is complete, before performing internal scheduler work (such as
     // starting the next client).
-    private final BaseClientMonitor.Callback mInternalCallback = new BaseClientMonitor.Callback() {
+    public class InternalCallback implements BaseClientMonitor.Callback {
         @Override
         public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
             Slog.d(getTag(), "[Started] " + clientMonitor);
@@ -168,11 +286,16 @@
                 mCoexCoordinator.addAuthenticationClient(mSensorType,
                         (AuthenticationClient<?>) clientMonitor);
             }
+
+            if (mCurrentOperation.mClientCallback != null) {
+                mCurrentOperation.mClientCallback.onClientStarted(clientMonitor);
+            }
         }
 
         @Override
         public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
             mHandler.post(() -> {
+                clientMonitor.destroy();
                 if (mCurrentOperation == null) {
                     Slog.e(getTag(), "[Finishing] " + clientMonitor
                             + " but current operation is null, success: " + success
@@ -180,9 +303,9 @@
                     return;
                 }
 
-                if (!mCurrentOperation.isFor(clientMonitor)) {
+                if (clientMonitor != mCurrentOperation.mClientMonitor) {
                     Slog.e(getTag(), "[Ignoring Finish] " + clientMonitor + " does not match"
-                            + " current: " + mCurrentOperation);
+                            + " current: " + mCurrentOperation.mClientMonitor);
                     return;
                 }
 
@@ -192,33 +315,36 @@
                             (AuthenticationClient<?>) clientMonitor);
                 }
 
+                mCurrentOperation.mState = Operation.STATE_FINISHED;
+
+                if (mCurrentOperation.mClientCallback != null) {
+                    mCurrentOperation.mClientCallback.onClientFinished(clientMonitor, success);
+                }
+
                 if (mGestureAvailabilityDispatcher != null) {
                     mGestureAvailabilityDispatcher.markSensorActive(
-                            mCurrentOperation.getSensorId(), false /* active */);
+                            mCurrentOperation.mClientMonitor.getSensorId(), false /* active */);
                 }
 
                 if (mRecentOperations.size() >= mRecentOperationsLimit) {
                     mRecentOperations.remove(0);
                 }
-                mRecentOperations.add(mCurrentOperation.getProtoEnum());
+                mRecentOperations.add(mCurrentOperation.mClientMonitor.getProtoEnum());
                 mCurrentOperation = null;
                 mTotalOperationsHandled++;
                 startNextOperationIfIdle();
             });
         }
-    };
+    }
 
     @VisibleForTesting
-    BiometricScheduler(@NonNull String tag,
-            @NonNull Handler handler,
-            @SensorType int sensorType,
+    BiometricScheduler(@NonNull String tag, @SensorType int sensorType,
             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
-            @NonNull IBiometricService biometricService,
-            int recentOperationsLimit,
+            @NonNull IBiometricService biometricService, int recentOperationsLimit,
             @NonNull CoexCoordinator coexCoordinator) {
         mBiometricTag = tag;
-        mHandler = handler;
         mSensorType = sensorType;
+        mInternalCallback = new InternalCallback();
         mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher;
         mPendingOperations = new ArrayDeque<>();
         mBiometricService = biometricService;
@@ -230,26 +356,24 @@
 
     /**
      * Creates a new scheduler.
-     *
      * @param tag for the specific instance of the scheduler. Should be unique.
-     * @param handler handler for callbacks (all methods of this class must be called on the
-     *                thread associated with this handler)
      * @param sensorType the sensorType that this scheduler is handling.
      * @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures
      *                                      (such as fingerprint swipe).
      */
     public BiometricScheduler(@NonNull String tag,
-            @NonNull Handler handler,
             @SensorType int sensorType,
             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
-        this(tag, handler, sensorType, gestureAvailabilityDispatcher,
-                IBiometricService.Stub.asInterface(
-                        ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
-                LOG_NUM_RECENT_OPERATIONS, CoexCoordinator.getInstance());
+        this(tag, sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface(
+                ServiceManager.getService(Context.BIOMETRIC_SERVICE)), LOG_NUM_RECENT_OPERATIONS,
+                CoexCoordinator.getInstance());
     }
 
-    @VisibleForTesting
-    public BaseClientMonitor.Callback getInternalCallback() {
+    /**
+     * @return A reference to the internal callback that should be invoked whenever the scheduler
+     *         needs to (e.g. client started, client finished).
+     */
+    @NonNull protected InternalCallback getInternalCallback() {
         return mInternalCallback;
     }
 
@@ -268,46 +392,72 @@
         }
 
         mCurrentOperation = mPendingOperations.poll();
+        final BaseClientMonitor currentClient = mCurrentOperation.mClientMonitor;
         Slog.d(getTag(), "[Polled] " + mCurrentOperation);
 
         // If the operation at the front of the queue has been marked for cancellation, send
         // ERROR_CANCELED. No need to start this client.
-        if (mCurrentOperation.isMarkedCanceling()) {
+        if (mCurrentOperation.mState == Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
             Slog.d(getTag(), "[Now Cancelling] " + mCurrentOperation);
-            mCurrentOperation.cancel(mHandler, mInternalCallback);
+            if (!(currentClient instanceof Interruptable)) {
+                throw new IllegalStateException("Mis-implemented client or scheduler, "
+                        + "trying to cancel non-interruptable operation: " + mCurrentOperation);
+            }
+
+            final Interruptable interruptable = (Interruptable) currentClient;
+            interruptable.cancelWithoutStarting(getInternalCallback());
             // Now we wait for the client to send its FinishCallback, which kicks off the next
             // operation.
             return;
         }
 
-        if (mGestureAvailabilityDispatcher != null && mCurrentOperation.isAcquisitionOperation()) {
+        if (mGestureAvailabilityDispatcher != null
+                && mCurrentOperation.mClientMonitor instanceof AcquisitionClient) {
             mGestureAvailabilityDispatcher.markSensorActive(
-                    mCurrentOperation.getSensorId(), true /* active */);
+                    mCurrentOperation.mClientMonitor.getSensorId(),
+                    true /* active */);
         }
 
         // Not all operations start immediately. BiometricPrompt waits for its operation
         // to arrive at the head of the queue, before pinging it to start.
-        final int cookie = mCurrentOperation.isReadyToStart();
-        if (cookie == 0) {
-            if (!mCurrentOperation.start(mInternalCallback)) {
+        final boolean shouldStartNow = currentClient.getCookie() == 0;
+        if (shouldStartNow) {
+            if (mCurrentOperation.isUnstartableHalOperation()) {
+                final HalClientMonitor<?> halClientMonitor =
+                        (HalClientMonitor<?>) mCurrentOperation.mClientMonitor;
                 // Note down current length of queue
                 final int pendingOperationsLength = mPendingOperations.size();
-                final BiometricSchedulerOperation lastOperation = mPendingOperations.peekLast();
+                final Operation lastOperation = mPendingOperations.peekLast();
                 Slog.e(getTag(), "[Unable To Start] " + mCurrentOperation
                         + ". Last pending operation: " + lastOperation);
 
+                // For current operations, 1) unableToStart, which notifies the caller-side, then
+                // 2) notify operation's callback, to notify applicable system service that the
+                // operation failed.
+                halClientMonitor.unableToStart();
+                if (mCurrentOperation.mClientCallback != null) {
+                    mCurrentOperation.mClientCallback.onClientFinished(
+                            mCurrentOperation.mClientMonitor, false /* success */);
+                }
+
                 // Then for each operation currently in the pending queue at the time of this
                 // failure, do the same as above. Otherwise, it's possible that something like
                 // setActiveUser fails, but then authenticate (for the wrong user) is invoked.
                 for (int i = 0; i < pendingOperationsLength; i++) {
-                    final BiometricSchedulerOperation operation = mPendingOperations.pollFirst();
-                    if (operation != null) {
-                        Slog.w(getTag(), "[Aborting Operation] " + operation);
-                        operation.abort();
-                    } else {
+                    final Operation operation = mPendingOperations.pollFirst();
+                    if (operation == null) {
                         Slog.e(getTag(), "Null operation, index: " + i
                                 + ", expected length: " + pendingOperationsLength);
+                        break;
                     }
+                    if (operation.isHalOperation()) {
+                        ((HalClientMonitor<?>) operation.mClientMonitor).unableToStart();
+                    }
+                    if (operation.mClientCallback != null) {
+                        operation.mClientCallback.onClientFinished(operation.mClientMonitor,
+                                false /* success */);
+                    }
+                    Slog.w(getTag(), "[Aborted Operation] " + operation);
                 }
 
                 // It's possible that during cleanup a new set of operations came in. We can try to
@@ -315,20 +465,25 @@
                 // actually be multiple operations (i.e. updateActiveUser + authenticate).
                 mCurrentOperation = null;
                 startNextOperationIfIdle();
+            } else {
+                Slog.d(getTag(), "[Starting] " + mCurrentOperation);
+                currentClient.start(getInternalCallback());
+                mCurrentOperation.mState = Operation.STATE_STARTED;
             }
         } else {
             try {
-                mBiometricService.onReadyForAuthentication(cookie);
+                mBiometricService.onReadyForAuthentication(currentClient.getCookie());
             } catch (RemoteException e) {
                 Slog.e(getTag(), "Remote exception when contacting BiometricService", e);
             }
             Slog.d(getTag(), "Waiting for cookie before starting: " + mCurrentOperation);
+            mCurrentOperation.mState = Operation.STATE_WAITING_FOR_COOKIE;
         }
     }
 
     /**
      * Starts the {@link #mCurrentOperation} if
-     * 1) its state is {@link BiometricSchedulerOperation#STATE_WAITING_FOR_COOKIE} and
+     * 1) its state is {@link Operation#STATE_WAITING_FOR_COOKIE} and
      * 2) its cookie matches this cookie
      *
      * This is currently only used by {@link com.android.server.biometrics.BiometricService}, which
@@ -344,13 +499,45 @@
             Slog.e(getTag(), "Current operation is null");
             return;
         }
+        if (mCurrentOperation.mState != Operation.STATE_WAITING_FOR_COOKIE) {
+            if (mCurrentOperation.mState == Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
+                Slog.d(getTag(), "Operation was marked for cancellation, cancelling now: "
+                        + mCurrentOperation);
+                // This should trigger the internal onClientFinished callback, which clears the
+                // operation and starts the next one.
+                final ErrorConsumer errorConsumer =
+                        (ErrorConsumer) mCurrentOperation.mClientMonitor;
+                errorConsumer.onError(BiometricConstants.BIOMETRIC_ERROR_CANCELED,
+                        0 /* vendorCode */);
+                return;
+            } else {
+                Slog.e(getTag(), "Operation is in the wrong state: " + mCurrentOperation
+                        + ", expected STATE_WAITING_FOR_COOKIE");
+                return;
+            }
+        }
+        if (mCurrentOperation.mClientMonitor.getCookie() != cookie) {
+            Slog.e(getTag(), "Mismatched cookie for operation: " + mCurrentOperation
+                    + ", received: " + cookie);
+            return;
+        }
 
-        if (mCurrentOperation.startWithCookie(mInternalCallback, cookie)) {
-            Slog.d(getTag(), "[Started] Prepared client: " + mCurrentOperation);
-        } else {
+        if (mCurrentOperation.isUnstartableHalOperation()) {
             Slog.e(getTag(), "[Unable To Start] Prepared client: " + mCurrentOperation);
+            // This is BiometricPrompt trying to auth but something's wrong with the HAL.
+            final HalClientMonitor<?> halClientMonitor =
+                    (HalClientMonitor<?>) mCurrentOperation.mClientMonitor;
+            halClientMonitor.unableToStart();
+            if (mCurrentOperation.mClientCallback != null) {
+                mCurrentOperation.mClientCallback.onClientFinished(mCurrentOperation.mClientMonitor,
+                        false /* success */);
+            }
             mCurrentOperation = null;
             startNextOperationIfIdle();
+        } else {
+            Slog.d(getTag(), "[Starting] Prepared client: " + mCurrentOperation);
+            mCurrentOperation.mState = Operation.STATE_STARTED;
+            mCurrentOperation.mClientMonitor.start(getInternalCallback());
         }
     }
 
@@ -375,13 +562,17 @@
         // pending clients as canceling. Once they reach the head of the queue, the scheduler will
         // send ERROR_CANCELED and skip the operation.
         if (clientMonitor.interruptsPrecedingClients()) {
-            for (BiometricSchedulerOperation operation : mPendingOperations) {
-                Slog.d(getTag(), "New client, marking pending op as canceling: " + operation);
-                operation.markCanceling();
+            for (Operation operation : mPendingOperations) {
+                if (operation.mClientMonitor instanceof Interruptable
+                        && operation.mState != Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
+                    Slog.d(getTag(), "New client incoming, marking pending client as canceling: "
+                            + operation.mClientMonitor);
+                    operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
+                }
             }
         }
 
-        mPendingOperations.add(new BiometricSchedulerOperation(clientMonitor, clientCallback));
+        mPendingOperations.add(new Operation(clientMonitor, clientCallback));
         Slog.d(getTag(), "[Added] " + clientMonitor
                 + ", new queue size: " + mPendingOperations.size());
 
@@ -389,34 +580,67 @@
         // cancellable, start the cancellation process.
         if (clientMonitor.interruptsPrecedingClients()
                 && mCurrentOperation != null
-                && mCurrentOperation.isInterruptable()
-                && mCurrentOperation.isStarted()) {
+                && mCurrentOperation.mClientMonitor instanceof Interruptable
+                && mCurrentOperation.mState == Operation.STATE_STARTED) {
             Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation);
-            mCurrentOperation.cancel(mHandler, mInternalCallback);
-        } else {
-            startNextOperationIfIdle();
+            cancelInternal(mCurrentOperation);
         }
+
+        startNextOperationIfIdle();
+    }
+
+    private void cancelInternal(Operation operation) {
+        if (operation != mCurrentOperation) {
+            Slog.e(getTag(), "cancelInternal invoked on non-current operation: " + operation);
+            return;
+        }
+        if (!(operation.mClientMonitor instanceof Interruptable)) {
+            Slog.w(getTag(), "Operation not interruptable: " + operation);
+            return;
+        }
+        if (operation.mState == Operation.STATE_STARTED_CANCELING) {
+            Slog.w(getTag(), "Cancel already invoked for operation: " + operation);
+            return;
+        }
+        if (operation.mState == Operation.STATE_WAITING_FOR_COOKIE) {
+            Slog.w(getTag(), "Skipping cancellation for non-started operation: " + operation);
+            // We can set it to null immediately, since the HAL was never notified to start.
+            if (mCurrentOperation != null) {
+                mCurrentOperation.mClientMonitor.destroy();
+            }
+            mCurrentOperation = null;
+            startNextOperationIfIdle();
+            return;
+        }
+        Slog.d(getTag(), "[Cancelling] Current client: " + operation.mClientMonitor);
+        final Interruptable interruptable = (Interruptable) operation.mClientMonitor;
+        interruptable.cancel();
+        operation.mState = Operation.STATE_STARTED_CANCELING;
+
+        // Add a watchdog. If the HAL does not acknowledge within the timeout, we will
+        // forcibly finish this client.
+        mHandler.postDelayed(new CancellationWatchdog(getTag(), operation),
+                CancellationWatchdog.DELAY_MS);
     }
 
     /**
      * Requests to cancel enrollment.
      * @param token from the caller, should match the token passed in when requesting enrollment
      */
-    public void cancelEnrollment(IBinder token, long requestId) {
-        Slog.d(getTag(), "cancelEnrollment, requestId: " + requestId);
-
-        if (mCurrentOperation != null
-                && canCancelEnrollOperation(mCurrentOperation, token, requestId)) {
-            Slog.d(getTag(), "Cancelling enrollment op: " + mCurrentOperation);
-            mCurrentOperation.cancel(mHandler, mInternalCallback);
-        } else {
-            for (BiometricSchedulerOperation operation : mPendingOperations) {
-                if (canCancelEnrollOperation(operation, token, requestId)) {
-                    Slog.d(getTag(), "Cancelling pending enrollment op: " + operation);
-                    operation.markCanceling();
-                }
-            }
+    public void cancelEnrollment(IBinder token) {
+        if (mCurrentOperation == null) {
+            Slog.e(getTag(), "Unable to cancel enrollment, null operation");
+            return;
         }
+        final boolean isEnrolling = mCurrentOperation.mClientMonitor instanceof EnrollClient;
+        final boolean tokenMatches = mCurrentOperation.mClientMonitor.getToken() == token;
+        if (!isEnrolling || !tokenMatches) {
+            Slog.w(getTag(), "Not cancelling enrollment, isEnrolling: " + isEnrolling
+                    + " tokenMatches: " + tokenMatches);
+            return;
+        }
+
+        cancelInternal(mCurrentOperation);
     }
 
     /**
@@ -425,42 +649,62 @@
      * @param requestId the id returned when requesting authentication
      */
     public void cancelAuthenticationOrDetection(IBinder token, long requestId) {
-        Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId);
+        Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId
+                + " current: " + mCurrentOperation
+                + " stack size: " + mPendingOperations.size());
 
         if (mCurrentOperation != null
                 && canCancelAuthOperation(mCurrentOperation, token, requestId)) {
-            Slog.d(getTag(), "Cancelling auth/detect op: " + mCurrentOperation);
-            mCurrentOperation.cancel(mHandler, mInternalCallback);
+            Slog.d(getTag(), "Cancelling: " + mCurrentOperation);
+            cancelInternal(mCurrentOperation);
         } else {
-            for (BiometricSchedulerOperation operation : mPendingOperations) {
+            // Look through the current queue for all authentication clients for the specified
+            // token, and mark them as STATE_WAITING_IN_QUEUE_CANCELING. Note that we're marking
+            // all of them, instead of just the first one, since the API surface currently doesn't
+            // allow us to distinguish between multiple authentication requests from the same
+            // process. However, this generally does not happen anyway, and would be a class of
+            // bugs on its own.
+            for (Operation operation : mPendingOperations) {
                 if (canCancelAuthOperation(operation, token, requestId)) {
-                    Slog.d(getTag(), "Cancelling pending auth/detect op: " + operation);
-                    operation.markCanceling();
+                    Slog.d(getTag(), "Marking " + operation
+                            + " as STATE_WAITING_IN_QUEUE_CANCELING");
+                    operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
                 }
             }
         }
     }
 
-    private static boolean canCancelEnrollOperation(BiometricSchedulerOperation operation,
-            IBinder token, long requestId) {
-        return operation.isEnrollOperation()
-                && operation.isMatchingToken(token)
-                && operation.isMatchingRequestId(requestId);
+    private static boolean canCancelAuthOperation(Operation operation, IBinder token,
+            long requestId) {
+        // TODO: restrict callers that can cancel without requestId (negative value)?
+        return isAuthenticationOrDetectionOperation(operation)
+                && operation.mClientMonitor.getToken() == token
+                && isMatchingRequestId(operation, requestId);
     }
 
-    private static boolean canCancelAuthOperation(BiometricSchedulerOperation operation,
-            IBinder token, long requestId) {
-        // TODO: restrict callers that can cancel without requestId (negative value)?
-        return operation.isAuthenticationOrDetectionOperation()
-                && operation.isMatchingToken(token)
-                && operation.isMatchingRequestId(requestId);
+    // By default, monitors are not associated with a request id to retain the original
+    // behavior (i.e. if no requestId is explicitly set then assume it matches)
+    private static boolean isMatchingRequestId(Operation operation, long requestId) {
+        return !operation.mClientMonitor.hasRequestId()
+                || operation.mClientMonitor.getRequestId() == requestId;
+    }
+
+    private static boolean isAuthenticationOrDetectionOperation(@NonNull Operation operation) {
+        final boolean isAuthentication =
+                operation.mClientMonitor instanceof AuthenticationConsumer;
+        final boolean isDetection =
+                operation.mClientMonitor instanceof DetectionConsumer;
+        return isAuthentication || isDetection;
     }
 
     /**
      * @return the current operation
      */
     public BaseClientMonitor getCurrentClient() {
-        return mCurrentOperation != null ? mCurrentOperation.getClientMonitor() : null;
+        if (mCurrentOperation == null) {
+            return null;
+        }
+        return mCurrentOperation.mClientMonitor;
     }
 
     public int getCurrentPendingCount() {
@@ -475,7 +719,7 @@
                 new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
         final String timestamp = dateFormat.format(new Date(System.currentTimeMillis()));
         final List<String> pendingOperations = new ArrayList<>();
-        for (BiometricSchedulerOperation operation : mPendingOperations) {
+        for (Operation operation : mPendingOperations) {
             pendingOperations.add(operation.toString());
         }
 
@@ -491,7 +735,7 @@
         pw.println("Type: " + mSensorType);
         pw.println("Current operation: " + mCurrentOperation);
         pw.println("Pending operations: " + mPendingOperations.size());
-        for (BiometricSchedulerOperation operation : mPendingOperations) {
+        for (Operation operation : mPendingOperations) {
             pw.println("Pending operation: " + operation);
         }
         for (CrashState crashState : mCrashStates) {
@@ -502,7 +746,7 @@
     public byte[] dumpProtoState(boolean clearSchedulerBuffer) {
         final ProtoOutputStream proto = new ProtoOutputStream();
         proto.write(BiometricSchedulerProto.CURRENT_OPERATION, mCurrentOperation != null
-                ? mCurrentOperation.getProtoEnum() : BiometricsProto.CM_NONE);
+                ? mCurrentOperation.mClientMonitor.getProtoEnum() : BiometricsProto.CM_NONE);
         proto.write(BiometricSchedulerProto.TOTAL_OPERATIONS, mTotalOperationsHandled);
 
         if (!mRecentOperations.isEmpty()) {
@@ -527,7 +771,6 @@
      * HAL dies.
      */
     public void reset() {
-        Slog.d(getTag(), "Resetting scheduler");
         mPendingOperations.clear();
         mCurrentOperation = null;
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
deleted file mode 100644
index a8cce15..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
+++ /dev/null
@@ -1,419 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics.sensors;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.hardware.biometrics.BiometricConstants;
-import android.os.Handler;
-import android.os.IBinder;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Contains all the necessary information for a HAL operation.
- */
-public class BiometricSchedulerOperation {
-    protected static final String TAG = "BiometricSchedulerOperation";
-
-    /**
-     * The operation is added to the list of pending operations and waiting for its turn.
-     */
-    protected static final int STATE_WAITING_IN_QUEUE = 0;
-
-    /**
-     * The operation is added to the list of pending operations, but a subsequent operation
-     * has been added. This state only applies to {@link Interruptable} operations. When this
-     * operation reaches the head of the queue, it will send ERROR_CANCELED and finish.
-     */
-    protected static final int STATE_WAITING_IN_QUEUE_CANCELING = 1;
-
-    /**
-     * The operation has reached the front of the queue and has started.
-     */
-    protected static final int STATE_STARTED = 2;
-
-    /**
-     * The operation was started, but is now canceling. Operations should wait for the HAL to
-     * acknowledge that the operation was canceled, at which point it finishes.
-     */
-    protected static final int STATE_STARTED_CANCELING = 3;
-
-    /**
-     * The operation has reached the head of the queue but is waiting for BiometricService
-     * to acknowledge and start the operation.
-     */
-    protected static final int STATE_WAITING_FOR_COOKIE = 4;
-
-    /**
-     * The {@link BaseClientMonitor.Callback} has been invoked and the client is finished.
-     */
-    protected static final int STATE_FINISHED = 5;
-
-    @IntDef({STATE_WAITING_IN_QUEUE,
-            STATE_WAITING_IN_QUEUE_CANCELING,
-            STATE_STARTED,
-            STATE_STARTED_CANCELING,
-            STATE_WAITING_FOR_COOKIE,
-            STATE_FINISHED})
-    @Retention(RetentionPolicy.SOURCE)
-    protected @interface OperationState {}
-
-    private static final int CANCEL_WATCHDOG_DELAY_MS = 3000;
-
-    @NonNull
-    private final BaseClientMonitor mClientMonitor;
-    @Nullable
-    private final BaseClientMonitor.Callback mClientCallback;
-    @OperationState
-    private int mState;
-    @VisibleForTesting
-    @NonNull
-    final Runnable mCancelWatchdog;
-
-    BiometricSchedulerOperation(
-            @NonNull BaseClientMonitor clientMonitor,
-            @Nullable BaseClientMonitor.Callback callback
-    ) {
-        this(clientMonitor, callback, STATE_WAITING_IN_QUEUE);
-    }
-
-    protected BiometricSchedulerOperation(
-            @NonNull BaseClientMonitor clientMonitor,
-            @Nullable BaseClientMonitor.Callback callback,
-            @OperationState int state
-    ) {
-        mClientMonitor = clientMonitor;
-        mClientCallback = callback;
-        mState = state;
-        mCancelWatchdog = () -> {
-            if (!isFinished()) {
-                Slog.e(TAG, "[Watchdog Triggered]: " + this);
-                getWrappedCallback().onClientFinished(mClientMonitor, false /* success */);
-            }
-        };
-    }
-
-    /**
-     * Zero if this operation is ready to start or has already started. A non-zero cookie
-     * is returned if the operation has not started and is waiting on
-     * {@link android.hardware.biometrics.IBiometricService#onReadyForAuthentication(int)}.
-     *
-     * @return cookie or 0 if ready/started
-     */
-    public int isReadyToStart() {
-        if (mState == STATE_WAITING_FOR_COOKIE || mState == STATE_WAITING_IN_QUEUE) {
-            final int cookie = mClientMonitor.getCookie();
-            if (cookie != 0) {
-                mState = STATE_WAITING_FOR_COOKIE;
-            }
-            return cookie;
-        }
-
-        return 0;
-    }
-
-    /**
-     * Start this operation without waiting for a cookie
-     * (i.e. {@link #isReadyToStart() returns zero}
-     *
-     * @param callback lifecycle callback
-     * @return if this operation started
-     */
-    public boolean start(@NonNull BaseClientMonitor.Callback callback) {
-        checkInState("start",
-                STATE_WAITING_IN_QUEUE,
-                STATE_WAITING_FOR_COOKIE,
-                STATE_WAITING_IN_QUEUE_CANCELING);
-
-        if (mClientMonitor.getCookie() != 0) {
-            throw new IllegalStateException("operation requires cookie");
-        }
-
-        return doStart(callback);
-    }
-
-    /**
-     * Start this operation after receiving the given cookie.
-     *
-     * @param callback lifecycle callback
-     * @param cookie   cookie indicting the operation should begin
-     * @return if this operation started
-     */
-    public boolean startWithCookie(@NonNull BaseClientMonitor.Callback callback, int cookie) {
-        checkInState("start",
-                STATE_WAITING_IN_QUEUE,
-                STATE_WAITING_FOR_COOKIE,
-                STATE_WAITING_IN_QUEUE_CANCELING);
-
-        if (mClientMonitor.getCookie() != cookie) {
-            Slog.e(TAG, "Mismatched cookie for operation: " + this + ", received: " + cookie);
-            return false;
-        }
-
-        return doStart(callback);
-    }
-
-    private boolean doStart(@NonNull BaseClientMonitor.Callback callback) {
-        final BaseClientMonitor.Callback cb = getWrappedCallback(callback);
-
-        if (mState == STATE_WAITING_IN_QUEUE_CANCELING) {
-            Slog.d(TAG, "Operation marked for cancellation, cancelling now: " + this);
-
-            cb.onClientFinished(mClientMonitor, true /* success */);
-            if (mClientMonitor instanceof ErrorConsumer) {
-                final ErrorConsumer errorConsumer = (ErrorConsumer) mClientMonitor;
-                errorConsumer.onError(BiometricConstants.BIOMETRIC_ERROR_CANCELED,
-                        0 /* vendorCode */);
-            } else {
-                Slog.w(TAG, "monitor cancelled but does not implement ErrorConsumer");
-            }
-
-            return false;
-        }
-
-        if (isUnstartableHalOperation()) {
-            Slog.v(TAG, "unable to start: " + this);
-            ((HalClientMonitor<?>) mClientMonitor).unableToStart();
-            cb.onClientFinished(mClientMonitor, false /* success */);
-            return false;
-        }
-
-        mState = STATE_STARTED;
-        mClientMonitor.start(cb);
-
-        Slog.v(TAG, "started: " + this);
-        return true;
-    }
-
-    /**
-     * Abort a pending operation.
-     *
-     * This is similar to cancel but the operation must not have been started. It will
-     * immediately abort the operation and notify the client that it has finished unsuccessfully.
-     */
-    public void abort() {
-        checkInState("cannot abort a non-pending operation",
-                STATE_WAITING_IN_QUEUE,
-                STATE_WAITING_FOR_COOKIE,
-                STATE_WAITING_IN_QUEUE_CANCELING);
-
-        if (isHalOperation()) {
-            ((HalClientMonitor<?>) mClientMonitor).unableToStart();
-        }
-        getWrappedCallback().onClientFinished(mClientMonitor, false /* success */);
-
-        Slog.v(TAG, "Aborted: " + this);
-    }
-
-    /** Flags this operation as canceled, but does not cancel it until started. */
-    public void markCanceling() {
-        if (mState == STATE_WAITING_IN_QUEUE && isInterruptable()) {
-            mState = STATE_WAITING_IN_QUEUE_CANCELING;
-            Slog.v(TAG, "Marked cancelling: " + this);
-        }
-    }
-
-    /**
-     * Cancel the operation now.
-     *
-     * @param handler handler to use for the cancellation watchdog
-     * @param callback lifecycle callback (only used if this operation hasn't started, otherwise
-     *                 the callback used from {@link #start(BaseClientMonitor.Callback)} is used)
-     */
-    public void cancel(@NonNull Handler handler, @NonNull BaseClientMonitor.Callback callback) {
-        checkNotInState("cancel", STATE_FINISHED);
-
-        final int currentState = mState;
-        if (!isInterruptable()) {
-            Slog.w(TAG, "Cannot cancel - operation not interruptable: " + this);
-            return;
-        }
-        if (currentState == STATE_STARTED_CANCELING) {
-            Slog.w(TAG, "Cannot cancel - already invoked for operation: " + this);
-            return;
-        }
-
-        mState = STATE_STARTED_CANCELING;
-        if (currentState == STATE_WAITING_IN_QUEUE
-                || currentState == STATE_WAITING_IN_QUEUE_CANCELING
-                || currentState == STATE_WAITING_FOR_COOKIE) {
-            Slog.d(TAG, "[Cancelling] Current client (without start): " + mClientMonitor);
-            ((Interruptable) mClientMonitor).cancelWithoutStarting(getWrappedCallback(callback));
-        } else {
-            Slog.d(TAG, "[Cancelling] Current client: " + mClientMonitor);
-            ((Interruptable) mClientMonitor).cancel();
-        }
-
-        // forcibly finish this client if the HAL does not acknowledge within the timeout
-        handler.postDelayed(mCancelWatchdog, CANCEL_WATCHDOG_DELAY_MS);
-    }
-
-    @NonNull
-    private BaseClientMonitor.Callback getWrappedCallback() {
-        return getWrappedCallback(null);
-    }
-
-    @NonNull
-    private BaseClientMonitor.Callback getWrappedCallback(
-            @Nullable BaseClientMonitor.Callback callback) {
-        final BaseClientMonitor.Callback destroyCallback = new BaseClientMonitor.Callback() {
-            @Override
-            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                    boolean success) {
-                mClientMonitor.destroy();
-                mState = STATE_FINISHED;
-            }
-        };
-        return new BaseClientMonitor.CompositeCallback(destroyCallback, callback, mClientCallback);
-    }
-
-    /** {@link BaseClientMonitor#getSensorId()}. */
-    public int getSensorId() {
-        return mClientMonitor.getSensorId();
-    }
-
-    /** {@link BaseClientMonitor#getProtoEnum()}. */
-    public int getProtoEnum() {
-        return mClientMonitor.getProtoEnum();
-    }
-
-    /** {@link BaseClientMonitor#getTargetUserId()}. */
-    public int getTargetUserId() {
-        return mClientMonitor.getTargetUserId();
-    }
-
-    /** If the given clientMonitor is the same as the one in the constructor. */
-    public boolean isFor(@NonNull BaseClientMonitor clientMonitor) {
-        return mClientMonitor == clientMonitor;
-    }
-
-    /** If this operation is {@link Interruptable}. */
-    public boolean isInterruptable() {
-        return mClientMonitor instanceof Interruptable;
-    }
-
-    private boolean isHalOperation() {
-        return mClientMonitor instanceof HalClientMonitor<?>;
-    }
-
-    private boolean isUnstartableHalOperation() {
-        if (isHalOperation()) {
-            final HalClientMonitor<?> client = (HalClientMonitor<?>) mClientMonitor;
-            if (client.getFreshDaemon() == null) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /** If this operation is an enrollment. */
-    public boolean isEnrollOperation() {
-        return mClientMonitor instanceof EnrollClient;
-    }
-
-    /** If this operation is authentication. */
-    public boolean isAuthenticateOperation() {
-        return mClientMonitor instanceof AuthenticationClient;
-    }
-
-    /** If this operation is authentication or detection. */
-    public boolean isAuthenticationOrDetectionOperation() {
-        final boolean isAuthentication = mClientMonitor instanceof AuthenticationConsumer;
-        final boolean isDetection = mClientMonitor instanceof DetectionConsumer;
-        return isAuthentication || isDetection;
-    }
-
-    /** If this operation performs acquisition {@link AcquisitionClient}. */
-    public boolean isAcquisitionOperation() {
-        return mClientMonitor instanceof AcquisitionClient;
-    }
-
-    /**
-     * If this operation matches the original requestId.
-     *
-     * By default, monitors are not associated with a request id to retain the original
-     * behavior (i.e. if no requestId is explicitly set then assume it matches)
-     *
-     * @param requestId a unique id {@link BaseClientMonitor#setRequestId(long)}.
-     */
-    public boolean isMatchingRequestId(long requestId) {
-        return !mClientMonitor.hasRequestId()
-                || mClientMonitor.getRequestId() == requestId;
-    }
-
-    /** If the token matches */
-    public boolean isMatchingToken(@Nullable IBinder token) {
-        return mClientMonitor.getToken() == token;
-    }
-
-    /** If this operation has started. */
-    public boolean isStarted() {
-        return mState == STATE_STARTED;
-    }
-
-    /** If this operation is cancelling but has not yet completed. */
-    public boolean isCanceling() {
-        return mState == STATE_STARTED_CANCELING;
-    }
-
-    /** If this operation has finished and completed its lifecycle. */
-    public boolean isFinished() {
-        return mState == STATE_FINISHED;
-    }
-
-    /** If {@link #markCanceling()} was called but the operation hasn't been canceled. */
-    public boolean isMarkedCanceling() {
-        return mState == STATE_WAITING_IN_QUEUE_CANCELING;
-    }
-
-    /**
-     * The monitor passed to the constructor.
-     * @deprecated avoid using and move to encapsulate within the operation
-     */
-    @Deprecated
-    public BaseClientMonitor getClientMonitor() {
-        return mClientMonitor;
-    }
-
-    private void checkNotInState(String message, @OperationState int... states) {
-        for (int state : states) {
-            if (mState == state) {
-                throw new IllegalStateException(message + ": illegal state= " + state);
-            }
-        }
-    }
-
-    private void checkInState(String message, @OperationState int... states) {
-        for (int state : states) {
-            if (mState == state) {
-                return;
-            }
-        }
-        throw new IllegalStateException(message + ": illegal state= " + mState);
-    }
-
-    @Override
-    public String toString() {
-        return mClientMonitor + ", State: " + mState;
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/Interruptable.java b/services/core/java/com/android/server/biometrics/sensors/Interruptable.java
index d5093c75..fab98b6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/Interruptable.java
+++ b/services/core/java/com/android/server/biometrics/sensors/Interruptable.java
@@ -32,11 +32,6 @@
      * {@link BaseClientMonitor#start(BaseClientMonitor.Callback)} was invoked. This usually happens
      * if the client is still waiting in the pending queue and got notified that a subsequent
      * operation is preempting it.
-     *
-     * This method must invoke
-     * {@link BaseClientMonitor.Callback#onClientFinished(BaseClientMonitor, boolean)} on the
-     * given callback (with success).
-     *
      * @param callback invoked when the operation is completed.
      */
     void cancelWithoutStarting(@NonNull BaseClientMonitor.Callback callback);
diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java b/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java
index f4997d4..92218b1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java
+++ b/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java
@@ -25,7 +25,11 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
 
 /**
  * Allows clients (such as keyguard) to register for notifications on when biometric lockout
@@ -37,7 +41,8 @@
     private static final String TAG = "LockoutResetTracker";
 
     private final Context mContext;
-    private final ArrayList<ClientCallback> mClientCallbacks;
+    @VisibleForTesting
+    final List<ClientCallback> mClientCallbacks = new ArrayList<>();
 
     private static class ClientCallback {
         private static final long WAKELOCK_TIMEOUT_MS = 2000;
@@ -81,7 +86,6 @@
 
     public LockoutResetDispatcher(Context context) {
         mContext = context;
-        mClientCallbacks = new ArrayList<>();
     }
 
     public void addCallback(IBiometricServiceLockoutResetCallback callback, String opPackageName) {
@@ -106,11 +110,13 @@
     @Override
     public void binderDied(IBinder who) {
         Slog.e(TAG, "Callback binder died: " + who);
-        for (ClientCallback callback : mClientCallbacks) {
+        final Iterator<ClientCallback> iterator = mClientCallbacks.iterator();
+        while (iterator.hasNext()) {
+            final ClientCallback callback = iterator.next();
             if (callback.mCallback.asBinder().equals(who)) {
                 Slog.e(TAG, "Removing dead callback for: " + callback.mOpPackageName);
                 callback.releaseWakelock();
-                mClientCallbacks.remove(callback);
+                iterator.remove();
             }
         }
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
index 19eaa17..b056bf8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -16,13 +16,10 @@
 
 package com.android.server.biometrics.sensors;
 
-import static com.android.server.biometrics.sensors.BiometricSchedulerOperation.STATE_STARTED;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.biometrics.IBiometricService;
-import android.os.Handler;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.util.Slog;
@@ -71,8 +68,9 @@
                     return;
                 }
 
-                Slog.d(getTag(), "[Client finished] " + clientMonitor + ", success: " + success);
-                if (mCurrentOperation != null && mCurrentOperation.isFor(mOwner)) {
+                Slog.d(getTag(), "[Client finished] "
+                        + clientMonitor + ", success: " + success);
+                if (mCurrentOperation != null && mCurrentOperation.mClientMonitor == mOwner) {
                     mCurrentOperation = null;
                     startNextOperationIfIdle();
                 } else {
@@ -85,31 +83,26 @@
     }
 
     @VisibleForTesting
-    UserAwareBiometricScheduler(@NonNull String tag,
-            @NonNull Handler handler,
-            @SensorType int sensorType,
+    UserAwareBiometricScheduler(@NonNull String tag, @SensorType int sensorType,
             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull IBiometricService biometricService,
             @NonNull CurrentUserRetriever currentUserRetriever,
             @NonNull UserSwitchCallback userSwitchCallback,
             @NonNull CoexCoordinator coexCoordinator) {
-        super(tag, handler, sensorType, gestureAvailabilityDispatcher, biometricService,
+        super(tag, sensorType, gestureAvailabilityDispatcher, biometricService,
                 LOG_NUM_RECENT_OPERATIONS, coexCoordinator);
 
         mCurrentUserRetriever = currentUserRetriever;
         mUserSwitchCallback = userSwitchCallback;
     }
 
-    public UserAwareBiometricScheduler(@NonNull String tag,
-            @NonNull Handler handler,
-            @SensorType int sensorType,
+    public UserAwareBiometricScheduler(@NonNull String tag, @SensorType int sensorType,
             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull CurrentUserRetriever currentUserRetriever,
             @NonNull UserSwitchCallback userSwitchCallback) {
-        this(tag, handler, sensorType, gestureAvailabilityDispatcher,
-                IBiometricService.Stub.asInterface(
-                        ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
-                currentUserRetriever, userSwitchCallback, CoexCoordinator.getInstance());
+        this(tag, sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface(
+                ServiceManager.getService(Context.BIOMETRIC_SERVICE)), currentUserRetriever,
+                userSwitchCallback, CoexCoordinator.getInstance());
     }
 
     @Override
@@ -129,7 +122,7 @@
         }
 
         final int currentUserId = mCurrentUserRetriever.getCurrentUserId();
-        final int nextUserId = mPendingOperations.getFirst().getTargetUserId();
+        final int nextUserId = mPendingOperations.getFirst().mClientMonitor.getTargetUserId();
 
         if (nextUserId == currentUserId) {
             super.startNextOperationIfIdle();
@@ -140,8 +133,8 @@
                     new ClientFinishedCallback(startClient);
 
             Slog.d(getTag(), "[Starting User] " + startClient);
-            mCurrentOperation = new BiometricSchedulerOperation(
-                    startClient, finishedCallback, STATE_STARTED);
+            mCurrentOperation = new Operation(
+                    startClient, finishedCallback, Operation.STATE_STARTED);
             startClient.start(finishedCallback);
         } else {
             if (mStopUserClient != null) {
@@ -154,8 +147,8 @@
 
                 Slog.d(getTag(), "[Stopping User] current: " + currentUserId
                         + ", next: " + nextUserId + ". " + mStopUserClient);
-                mCurrentOperation = new BiometricSchedulerOperation(
-                        mStopUserClient, finishedCallback, STATE_STARTED);
+                mCurrentOperation = new Operation(
+                        mStopUserClient, finishedCallback, Operation.STATE_STARTED);
                 mStopUserClient.start(finishedCallback);
             }
         }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 039b08e..675ee545 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -213,7 +213,7 @@
         }
 
         @Override // Binder call
-        public long enroll(int userId, final IBinder token, final byte[] hardwareAuthToken,
+        public void enroll(int userId, final IBinder token, final byte[] hardwareAuthToken,
                 final IFaceServiceReceiver receiver, final String opPackageName,
                 final int[] disabledFeatures, Surface previewSurface, boolean debugConsent) {
             Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
@@ -221,24 +221,23 @@
             final Pair<Integer, ServiceProvider> provider = getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for enroll");
-                return -1;
+                return;
             }
 
-            return provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
+            provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
                     receiver, opPackageName, disabledFeatures, previewSurface, debugConsent);
         }
 
         @Override // Binder call
-        public long enrollRemotely(int userId, final IBinder token, final byte[] hardwareAuthToken,
+        public void enrollRemotely(int userId, final IBinder token, final byte[] hardwareAuthToken,
                 final IFaceServiceReceiver receiver, final String opPackageName,
                 final int[] disabledFeatures) {
             Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
             // TODO(b/145027036): Implement this.
-            return -1;
         }
 
         @Override // Binder call
-        public void cancelEnrollment(final IBinder token, long requestId) {
+        public void cancelEnrollment(final IBinder token) {
             Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
 
             final Pair<Integer, ServiceProvider> provider = getSingleProvider();
@@ -247,7 +246,7 @@
                 return;
             }
 
-            provider.second.cancelEnrollment(provider.first, token, requestId);
+            provider.second.cancelEnrollment(provider.first, token);
         }
 
         @Override // Binder call
@@ -625,7 +624,7 @@
         private void addHidlProviders(@NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
             for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
                 mServiceProviders.add(
-                        Face10.newInstance(getContext(), hidlSensor, mLockoutResetDispatcher));
+                        new Face10(getContext(), hidlSensor, mLockoutResetDispatcher));
             }
         }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index 77e431c..e099ba3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -94,12 +94,12 @@
     void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
             @NonNull String opPackageName, long challenge);
 
-    long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
+    void scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
             int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName,
             @NonNull int[] disabledFeatures, @Nullable Surface previewSurface,
             boolean debugConsent);
 
-    void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId);
+    void cancelEnrollment(int sensorId, @NonNull IBinder token);
 
     long scheduleFaceDetect(int sensorId, @NonNull IBinder token, int userId,
             @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index aae4fbe..a806277 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -82,14 +82,13 @@
 
     FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
-            @NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, long requestId,
+            @NonNull byte[] hardwareAuthToken, @NonNull String opPackageName,
             @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
             @Nullable Surface previewSurface, int sensorId, int maxTemplatesPerUser,
             boolean debugConsent) {
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils,
                 timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
                 false /* shouldVibrate */);
-        setRequestId(requestId);
         mEnrollIgnoreList = getContext().getResources()
                 .getIntArray(R.array.config_face_acquire_enroll_ignorelist);
         mEnrollIgnoreListVendor = getContext().getResources()
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index ae507ab..4bae775 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -327,18 +327,17 @@
     }
 
     @Override
-    public long scheduleEnroll(int sensorId, @NonNull IBinder token,
+    public void scheduleEnroll(int sensorId, @NonNull IBinder token,
             @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
             @NonNull String opPackageName, @NonNull int[] disabledFeatures,
             @Nullable Surface previewSurface, boolean debugConsent) {
-        final long id = mRequestCounter.incrementAndGet();
         mHandler.post(() -> {
             final int maxTemplatesPerUser = mSensors.get(
                     sensorId).getSensorProperties().maxEnrollmentsPerUser;
             final FaceEnrollClient client = new FaceEnrollClient(mContext,
                     mSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
-                    opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures,
+                    opPackageName, FaceUtils.getInstance(sensorId), disabledFeatures,
                     ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser,
                     debugConsent);
             scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() {
@@ -352,13 +351,11 @@
                 }
             });
         });
-        return id;
     }
 
     @Override
-    public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
-        mHandler.post(() ->
-                mSensors.get(sensorId).getScheduler().cancelEnrollment(token, requestId));
+    public void cancelEnrollment(int sensorId, @NonNull IBinder token) {
+        mHandler.post(() -> mSensors.get(sensorId).getScheduler().cancelEnrollment(token));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 3927043..206b8f0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -494,7 +494,7 @@
         mToken = new Binder();
         mHandler = handler;
         mSensorProperties = sensorProperties;
-        mScheduler = new UserAwareBiometricScheduler(tag, mHandler,
+        mScheduler = new UserAwareBiometricScheduler(tag,
                 BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */,
                 () -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL,
                 new UserAwareBiometricScheduler.UserSwitchCallback() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 493c0a0..f4dcbbb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -333,13 +333,12 @@
     Face10(@NonNull Context context,
             @NonNull FaceSensorPropertiesInternal sensorProps,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-            @NonNull Handler handler,
             @NonNull BiometricScheduler scheduler) {
         mSensorProperties = sensorProps;
         mContext = context;
         mSensorId = sensorProps.sensorId;
         mScheduler = scheduler;
-        mHandler = handler;
+        mHandler = new Handler(Looper.getMainLooper());
         mUsageStats = new UsageStats(context);
         mAuthenticatorIds = new HashMap<>();
         mLazyDaemon = Face10.this::getDaemon;
@@ -358,12 +357,10 @@
         }
     }
 
-    public static Face10 newInstance(@NonNull Context context,
-            @NonNull FaceSensorPropertiesInternal sensorProps,
+    public Face10(@NonNull Context context, @NonNull FaceSensorPropertiesInternal sensorProps,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
-        final Handler handler = new Handler(Looper.getMainLooper());
-        return new Face10(context, sensorProps, lockoutResetDispatcher, handler,
-                new BiometricScheduler(TAG, handler, BiometricScheduler.SENSOR_TYPE_FACE,
+        this(context, sensorProps, lockoutResetDispatcher,
+                new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE,
                         null /* gestureAvailabilityTracker */));
     }
 
@@ -576,11 +573,10 @@
     }
 
     @Override
-    public long scheduleEnroll(int sensorId, @NonNull IBinder token,
+    public void scheduleEnroll(int sensorId, @NonNull IBinder token,
             @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
             @NonNull String opPackageName, @NonNull int[] disabledFeatures,
             @Nullable Surface previewSurface, boolean debugConsent) {
-        final long id = mRequestCounter.incrementAndGet();
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
@@ -588,7 +584,7 @@
 
             final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
-                    opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
+                    opPackageName, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
                     ENROLL_TIMEOUT_SEC, previewSurface, mSensorId);
 
             mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
@@ -602,12 +598,13 @@
                 }
             });
         });
-        return id;
     }
 
     @Override
-    public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
-        mHandler.post(() -> mScheduler.cancelEnrollment(token, requestId));
+    public void cancelEnrollment(int sensorId, @NonNull IBinder token) {
+        mHandler.post(() -> {
+            mScheduler.cancelEnrollment(token);
+        });
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
index 31e5c86..80828cced 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
@@ -53,13 +53,12 @@
 
     FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
-            @NonNull byte[] hardwareAuthToken, @NonNull String owner, long requestId,
+            @NonNull byte[] hardwareAuthToken, @NonNull String owner,
             @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
             @Nullable Surface previewSurface, int sensorId) {
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
                 timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
                 false /* shouldVibrate */);
-        setRequestId(requestId);
         mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length);
         mEnrollIgnoreList = getContext().getResources()
                 .getIntArray(R.array.config_face_acquire_enroll_ignorelist);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 6366e19..3e70ee5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -249,7 +249,7 @@
         }
 
         @Override // Binder call
-        public long enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken,
+        public void enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken,
                 final int userId, final IFingerprintServiceReceiver receiver,
                 final String opPackageName, @FingerprintManager.EnrollReason int enrollReason) {
             Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
@@ -257,15 +257,15 @@
             final Pair<Integer, ServiceProvider> provider = getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for enroll");
-                return -1;
+                return;
             }
 
-            return provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
+            provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
                     receiver, opPackageName, enrollReason);
         }
 
         @Override // Binder call
-        public void cancelEnrollment(final IBinder token, long requestId) {
+        public void cancelEnrollment(final IBinder token) {
             Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
 
             final Pair<Integer, ServiceProvider> provider = getSingleProvider();
@@ -274,7 +274,7 @@
                 return;
             }
 
-            provider.second.cancelEnrollment(provider.first, token, requestId);
+            provider.second.cancelEnrollment(provider.first, token);
         }
 
         @SuppressWarnings("deprecation")
@@ -818,7 +818,7 @@
                             mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
                 } else {
                     fingerprint21 = Fingerprint21.newInstance(getContext(),
-                            mFingerprintStateCallback, hidlSensor, mHandler,
+                            mFingerprintStateCallback, hidlSensor,
                             mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
                 }
                 mServiceProviders.add(fingerprint21);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 535705c..1772f81 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -88,11 +88,11 @@
     /**
      * Schedules fingerprint enrollment.
      */
-    long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
+    void scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
             int userId, @NonNull IFingerprintServiceReceiver receiver,
             @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason);
 
-    void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId);
+    void cancelEnrollment(int sensorId, @NonNull IBinder token);
 
     long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
             @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 67507cc..ccb34aa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -57,7 +57,7 @@
     private boolean mIsPointerDown;
 
     FingerprintEnrollClient(@NonNull Context context,
-            @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, long requestId,
+            @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
             @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String owner,
             @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
@@ -69,7 +69,6 @@
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
                 0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
                 !sensorProps.isAnyUdfpsType() /* shouldVibrate */);
-        setRequestId(requestId);
         mSensorProps = sensorProps;
         mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
         mMaxTemplatesPerUser = maxTemplatesPerUser;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index eb16c76..734b173 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -347,16 +347,15 @@
     }
 
     @Override
-    public long scheduleEnroll(int sensorId, @NonNull IBinder token,
+    public void scheduleEnroll(int sensorId, @NonNull IBinder token,
             @NonNull byte[] hardwareAuthToken, int userId,
             @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
             @FingerprintManager.EnrollReason int enrollReason) {
-        final long id = mRequestCounter.incrementAndGet();
         mHandler.post(() -> {
             final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties()
                     .maxEnrollmentsPerUser;
             final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
-                    mSensors.get(sensorId).getLazySession(), token, id,
+                    mSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
                     opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
                     mSensors.get(sensorId).getSensorProperties(),
@@ -379,13 +378,11 @@
                 }
             });
         });
-        return id;
     }
 
     @Override
-    public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
-        mHandler.post(() ->
-                mSensors.get(sensorId).getScheduler().cancelEnrollment(token, requestId));
+    public void cancelEnrollment(int sensorId, @NonNull IBinder token) {
+        mHandler.post(() -> mSensors.get(sensorId).getScheduler().cancelEnrollment(token));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 256761a..59e4b58 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -449,7 +449,7 @@
         mHandler = handler;
         mSensorProperties = sensorProperties;
         mLockoutCache = new LockoutCache();
-        mScheduler = new UserAwareBiometricScheduler(tag, handler,
+        mScheduler = new UserAwareBiometricScheduler(tag,
                 BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties),
                 gestureAvailabilityDispatcher,
                 () -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index d352cda..5f2f4cf 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -42,6 +42,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IHwBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -319,8 +320,7 @@
     Fingerprint21(@NonNull Context context,
             @NonNull FingerprintStateCallback fingerprintStateCallback,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
-            @NonNull BiometricScheduler scheduler,
-            @NonNull Handler handler,
+            @NonNull BiometricScheduler scheduler, @NonNull Handler handler,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull HalResultController controller) {
         mContext = context;
@@ -356,15 +356,16 @@
     public static Fingerprint21 newInstance(@NonNull Context context,
             @NonNull FingerprintStateCallback fingerprintStateCallback,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
-            @NonNull Handler handler,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+        final Handler handler = new Handler(Looper.getMainLooper());
         final BiometricScheduler scheduler =
-                new BiometricScheduler(TAG, handler,
+                new BiometricScheduler(TAG,
                         BiometricScheduler.sensorTypeFromFingerprintProperties(sensorProps),
                         gestureAvailabilityDispatcher);
         final HalResultController controller = new HalResultController(sensorProps.sensorId,
-                context, handler, scheduler);
+                context, handler,
+                scheduler);
         return new Fingerprint21(context, fingerprintStateCallback, sensorProps, scheduler, handler,
                 lockoutResetDispatcher, controller);
     }
@@ -557,20 +558,18 @@
     }
 
     @Override
-    public long scheduleEnroll(int sensorId, @NonNull IBinder token,
+    public void scheduleEnroll(int sensorId, @NonNull IBinder token,
             @NonNull byte[] hardwareAuthToken, int userId,
             @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
             @FingerprintManager.EnrollReason int enrollReason) {
-        final long id = mRequestCounter.incrementAndGet();
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
             final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
-                    mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver),
-                    userId, hardwareAuthToken, opPackageName,
-                    FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
-                    mSensorProperties.sensorId, mUdfpsOverlayController, mSidefpsController,
-                    enrollReason);
+                    mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
+                    hardwareAuthToken, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
+                    ENROLL_TIMEOUT_SEC, mSensorProperties.sensorId, mUdfpsOverlayController,
+                    mSidefpsController, enrollReason);
             mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
                 @Override
                 public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
@@ -589,12 +588,13 @@
                 }
             });
         });
-        return id;
     }
 
     @Override
-    public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
-        mHandler.post(() -> mScheduler.cancelEnrollment(token, requestId));
+    public void cancelEnrollment(int sensorId, @NonNull IBinder token) {
+        mHandler.post(() -> {
+            mScheduler.cancelEnrollment(token);
+        });
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 20dab55..dd68b4d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -26,6 +26,7 @@
 import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintStateListener;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Handler;
 import android.os.IBinder;
@@ -134,17 +135,43 @@
     @NonNull private final RestartAuthRunnable mRestartAuthRunnable;
 
     private static class TestableBiometricScheduler extends BiometricScheduler {
+        @NonNull private final TestableInternalCallback mInternalCallback;
         @NonNull private Fingerprint21UdfpsMock mFingerprint21;
 
-        TestableBiometricScheduler(@NonNull String tag, @NonNull Handler handler,
+        TestableBiometricScheduler(@NonNull String tag,
                 @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
-            super(tag, handler, BiometricScheduler.SENSOR_TYPE_FP_OTHER,
+            super(tag, BiometricScheduler.SENSOR_TYPE_FP_OTHER,
                     gestureAvailabilityDispatcher);
+            mInternalCallback = new TestableInternalCallback();
+        }
+
+        class TestableInternalCallback extends InternalCallback {
+            @Override
+            public void onClientStarted(BaseClientMonitor clientMonitor) {
+                super.onClientStarted(clientMonitor);
+                Slog.d(TAG, "Client started: " + clientMonitor);
+                mFingerprint21.setDebugMessage("Started: " + clientMonitor);
+            }
+
+            @Override
+            public void onClientFinished(BaseClientMonitor clientMonitor, boolean success) {
+                super.onClientFinished(clientMonitor, success);
+                Slog.d(TAG, "Client finished: " + clientMonitor);
+                mFingerprint21.setDebugMessage("Finished: " + clientMonitor);
+            }
         }
 
         void init(@NonNull Fingerprint21UdfpsMock fingerprint21) {
             mFingerprint21 = fingerprint21;
         }
+
+        /**
+         * Expose the internal finish callback so it can be used for testing
+         */
+        @Override
+        @NonNull protected InternalCallback getInternalCallback() {
+            return mInternalCallback;
+        }
     }
 
     /**
@@ -253,7 +280,7 @@
 
         final Handler handler = new Handler(Looper.getMainLooper());
         final TestableBiometricScheduler scheduler =
-                new TestableBiometricScheduler(TAG, handler, gestureAvailabilityDispatcher);
+                new TestableBiometricScheduler(TAG, gestureAvailabilityDispatcher);
         final MockHalResultController controller =
                 new MockHalResultController(sensorProps.sensorId, context, handler, scheduler);
         return new Fingerprint21UdfpsMock(context, fingerprintStateCallback, sensorProps, scheduler,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index cc50bdf..1ebf44c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -55,7 +55,7 @@
 
     FingerprintEnrollClient(@NonNull Context context,
             @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
-            long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId,
+            @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String owner,
             @NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
@@ -64,7 +64,6 @@
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
                 timeoutSec, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
                 true /* shouldVibrate */);
-        setRequestId(requestId);
         mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
 
         mEnrollReason = enrollReason;
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index e120343..582dd7c 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -614,9 +614,6 @@
         mEmulatorClipboardMonitor.accept(clip);
 
         final int userId = UserHandle.getUserId(uid);
-        if (clip != null) {
-            startClassificationLocked(clip, userId);
-        }
 
         // Update this user
         setPrimaryClipInternalLocked(getClipboardLocked(userId), clip, uid, sourcePackage);
@@ -672,6 +669,17 @@
     @GuardedBy("mLock")
     private void setPrimaryClipInternalLocked(PerUserClipboard clipboard, @Nullable ClipData clip,
             int uid, @Nullable String sourcePackage) {
+        final int userId = UserHandle.getUserId(uid);
+        if (clip != null) {
+            startClassificationLocked(clip, userId);
+        }
+
+        setPrimaryClipInternalNoClassifyLocked(clipboard, clip, uid, sourcePackage);
+    }
+
+    @GuardedBy("mLock")
+    private void setPrimaryClipInternalNoClassifyLocked(PerUserClipboard clipboard,
+            @Nullable ClipData clip, int uid, @Nullable String sourcePackage) {
         revokeUris(clipboard);
         clipboard.activePermissionOwners.clear();
         if (clip == null && clipboard.primaryClip == null) {
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index 39fa3f2..135276e 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -50,6 +50,12 @@
     public abstract void onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId);
 
     /**
+     * Returns the flags that should be added to any virtual displays created on this virtual
+     * device.
+     */
+    public abstract int getBaseVirtualDisplayFlags(IVirtualDevice virtualDevice);
+
+    /**
      * Returns true if the given {@code uid} is the owner of any virtual devices that are
      * currently active.
      */
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 6a716cb..066c263 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1223,8 +1223,11 @@
             for (RouteInfo route : mConfig.routes) {
                 lp.addRoute(route);
                 InetAddress address = route.getDestination().getAddress();
-                allowIPv4 |= address instanceof Inet4Address;
-                allowIPv6 |= address instanceof Inet6Address;
+
+                if (route.getType() == RouteInfo.RTN_UNICAST) {
+                    allowIPv4 |= address instanceof Inet4Address;
+                    allowIPv6 |= address instanceof Inet6Address;
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index be889e4..c558f91 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1250,6 +1250,14 @@
         }
         final Surface surface = virtualDisplayConfig.getSurface();
         int flags = virtualDisplayConfig.getFlags();
+        if (virtualDevice != null) {
+            final VirtualDeviceManagerInternal vdm =
+                    getLocalService(VirtualDeviceManagerInternal.class);
+            if (!vdm.isValidVirtualDevice(virtualDevice)) {
+                throw new SecurityException("Invalid virtual device");
+            }
+            flags |= vdm.getBaseVirtualDisplayFlags(virtualDevice);
+        }
 
         if (surface != null && surface.isSingleBuffered()) {
             throw new IllegalArgumentException("Surface can't be single-buffered");
@@ -1282,14 +1290,6 @@
             }
         }
 
-        if (virtualDevice != null) {
-            final VirtualDeviceManagerInternal vdm =
-                    getLocalService(VirtualDeviceManagerInternal.class);
-            if (!vdm.isValidVirtualDevice(virtualDevice)) {
-                throw new SecurityException("Invalid virtual device");
-            }
-        }
-
         if (callingUid != Process.SYSTEM_UID
                 && (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
             if (!canProjectVideo(projection)) {
diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
index b2bd47b..134fb96 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
@@ -21,7 +21,6 @@
 import static com.android.server.locales.LocaleManagerService.DEBUG;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.backup.BackupManager;
 import android.content.BroadcastReceiver;
@@ -34,7 +33,6 @@
 import android.content.pm.PackageManagerInternal;
 import android.os.BestClock;
 import android.os.Binder;
-import android.os.Environment;
 import android.os.HandlerThread;
 import android.os.LocaleList;
 import android.os.Process;
@@ -42,7 +40,6 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.text.TextUtils;
-import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TypedXmlPullParser;
@@ -53,17 +50,12 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.util.XmlUtils;
 
-import libcore.io.IoUtils;
-
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
 import java.nio.charset.StandardCharsets;
@@ -71,8 +63,6 @@
 import java.time.Duration;
 import java.time.ZoneOffset;
 import java.util.HashMap;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * Helper class for managing backup and restore of app-specific locales.
@@ -87,33 +77,28 @@
     private static final String ATTR_LOCALES = "locales";
     private static final String ATTR_CREATION_TIME_MILLIS = "creationTimeMillis";
 
-    private static final String STAGE_FILE_NAME = "staged_locales";
     private static final String SYSTEM_BACKUP_PACKAGE_KEY = "android";
-
-    private static final Pattern STAGE_FILE_NAME_PATTERN = Pattern.compile(
-            TextUtils.formatSimple("(^%s_)(\\d+)(\\.xml$)", STAGE_FILE_NAME));
-    private static final int USER_ID_GROUP_INDEX_IN_PATTERN = 2;
-    private static final Duration STAGE_FILE_RETENTION_PERIOD = Duration.ofDays(3);
+    // Stage data would be deleted on reboot since it's stored in memory. So it's retained until
+    // retention period OR next reboot, whichever happens earlier.
+    private static final Duration STAGE_DATA_RETENTION_PERIOD = Duration.ofDays(3);
 
     private final LocaleManagerService mLocaleManagerService;
     private final PackageManagerInternal mPackageManagerInternal;
-    private final File mStagedLocalesDir;
     private final Clock mClock;
     private final Context mContext;
     private final Object mStagedDataLock = new Object();
 
     // Staged data map keyed by user-id to handle multi-user scenario / work profiles. We are using
     // SparseArray because it is more memory-efficient than a HashMap.
-    private final SparseArray<StagedData> mStagedData = new SparseArray<>();
+    private final SparseArray<StagedData> mStagedData;
 
     private final PackageMonitor mPackageMonitor;
     private final BroadcastReceiver mUserMonitor;
 
     LocaleManagerBackupHelper(LocaleManagerService localeManagerService,
             PackageManagerInternal pmInternal) {
-        this(localeManagerService.mContext, localeManagerService, pmInternal,
-                new File(Environment.getDataSystemCeDirectory(),
-                        "app_locales"), getDefaultClock());
+        this(localeManagerService.mContext, localeManagerService, pmInternal, getDefaultClock(),
+                new SparseArray<>());
     }
 
     private static @NonNull Clock getDefaultClock() {
@@ -123,14 +108,12 @@
 
     @VisibleForTesting LocaleManagerBackupHelper(Context context,
             LocaleManagerService localeManagerService,
-            PackageManagerInternal pmInternal, File stagedLocalesDir, Clock clock) {
+            PackageManagerInternal pmInternal, Clock clock, SparseArray<StagedData> stagedData) {
         mContext = context;
         mLocaleManagerService = localeManagerService;
         mPackageManagerInternal = pmInternal;
         mClock = clock;
-        mStagedLocalesDir = stagedLocalesDir;
-
-        loadAllStageFiles();
+        mStagedData = stagedData;
 
         HandlerThread broadcastHandlerThread = new HandlerThread(TAG,
                 Process.THREAD_PRIORITY_BACKGROUND);
@@ -158,67 +141,6 @@
     }
 
     /**
-     * Loads the staged data into memory by reading all the files in the staged directory.
-     *
-     * <p><b>Note:</b> We don't ned to hold the lock here because this is only called in the
-     * constructor (before any broadcast receivers are registered).
-     */
-    private void loadAllStageFiles() {
-        File[] files = mStagedLocalesDir.listFiles();
-        if (files == null) {
-            return;
-        }
-        for (File file : files) {
-            String fileName = file.getName();
-            Matcher matcher = STAGE_FILE_NAME_PATTERN.matcher(fileName);
-            if (!matcher.matches()) {
-                file.delete();
-                Slog.w(TAG, TextUtils.formatSimple("Deleted %s. Reason: %s.", fileName,
-                        "Unrecognized file"));
-                continue;
-            }
-            try {
-                final int userId = Integer.parseInt(matcher.group(USER_ID_GROUP_INDEX_IN_PATTERN));
-                StagedData stagedData = readStageFile(file);
-                if (stagedData != null) {
-                    mStagedData.put(userId, stagedData);
-                } else {
-                    file.delete();
-                    Slog.w(TAG, TextUtils.formatSimple("Deleted %s. Reason: %s.", fileName,
-                            "Could not read file"));
-                }
-            } catch (NumberFormatException e) {
-                file.delete();
-                Slog.w(TAG, TextUtils.formatSimple("Deleted %s. Reason: %s.", fileName,
-                        "Could not parse user id from file name"));
-            }
-        }
-    }
-
-    /**
-     * Loads the stage file from the disk and parses it into a list of app backups.
-     */
-    private @Nullable StagedData readStageFile(@NonNull File file) {
-        InputStream stagedDataInputStream = null;
-        AtomicFile stageFile = new AtomicFile(file);
-        try {
-            stagedDataInputStream = stageFile.openRead();
-            final TypedXmlPullParser parser = Xml.newFastPullParser();
-            parser.setInput(stagedDataInputStream, StandardCharsets.UTF_8.name());
-
-            XmlUtils.beginDocument(parser, LOCALES_XML_TAG);
-            long creationTimeMillis = parser.getAttributeLong(/* namespace= */ null,
-                    ATTR_CREATION_TIME_MILLIS);
-            return new StagedData(creationTimeMillis, readFromXml(parser));
-        } catch (IOException | XmlPullParserException e) {
-            Slog.e(TAG, "Could not parse stage file ", e);
-        } finally {
-            IoUtils.closeQuietly(stagedDataInputStream);
-        }
-        return null;
-    }
-
-    /**
      * @see LocaleManagerInternal#getBackupPayload(int userId)
      */
     public byte[] getBackupPayload(int userId) {
@@ -261,9 +183,7 @@
 
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
         try {
-            // Passing arbitrary value for creationTimeMillis since it is ignored when forStage
-            // is false.
-            writeToXml(out, pkgStates, /* forStage= */ false, /* creationTimeMillis= */ -1);
+            writeToXml(out, pkgStates);
         } catch (IOException e) {
             Slog.e(TAG, "Could not write to xml for backup ", e);
             return null;
@@ -284,7 +204,7 @@
             int userId = mStagedData.keyAt(i);
             StagedData stagedData = mStagedData.get(userId);
             if (stagedData.mCreationTimeMillis
-                    < mClock.millis() - STAGE_FILE_RETENTION_PERIOD.toMillis()) {
+                    < mClock.millis() - STAGE_DATA_RETENTION_PERIOD.toMillis()) {
                 deleteStagedDataLocked(userId);
             }
         }
@@ -305,7 +225,7 @@
 
         final ByteArrayInputStream inputStream = new ByteArrayInputStream(payload);
 
-        HashMap<String, String> pkgStates = new HashMap<>();
+        HashMap<String, String> pkgStates;
         try {
             // Parse the input blob into a list of BackupPackageState.
             final TypedXmlPullParser parser = Xml.newFastPullParser();
@@ -315,6 +235,7 @@
             pkgStates = readFromXml(parser);
         } catch (IOException | XmlPullParserException e) {
             Slog.e(TAG, "Could not parse payload ", e);
+            return;
         }
 
         // We need a lock here to prevent race conditions when accessing the stage file.
@@ -323,7 +244,7 @@
         // performed simultaneously.
         synchronized (mStagedDataLock) {
             // Backups for apps which are yet to be installed.
-            mStagedData.put(userId, new StagedData(mClock.millis(), new HashMap<>()));
+            StagedData stagedData = new StagedData(mClock.millis(), new HashMap<>());
 
             for (String pkgName : pkgStates.keySet()) {
                 String languageTags = pkgStates.get(pkgName);
@@ -333,7 +254,7 @@
                     checkExistingLocalesAndApplyRestore(pkgName, languageTags, userId);
                 } else {
                     // Stage the data if the app isn't installed.
-                    mStagedData.get(userId).mPackageStates.put(pkgName, languageTags);
+                    stagedData.mPackageStates.put(pkgName, languageTags);
                     if (DEBUG) {
                         Slog.d(TAG, "Add locales=" + languageTags
                                 + " package=" + pkgName + " for lazy restore.");
@@ -341,7 +262,9 @@
                 }
             }
 
-            writeStageFileLocked(userId);
+            if (!stagedData.mPackageStates.isEmpty()) {
+                mStagedData.put(userId, stagedData);
+            }
         }
     }
 
@@ -396,55 +319,10 @@
         }
     }
 
-    /**
-     * Converts the list of app backups into xml and writes it onto the disk.
-     */
-    private void writeStageFileLocked(int userId) {
-        StagedData stagedData = mStagedData.get(userId);
-        if (stagedData.mPackageStates.isEmpty()) {
-            deleteStagedDataLocked(userId);
-            return;
-        }
-
-        final FileOutputStream stagedDataOutputStream;
-        AtomicFile stageFile = new AtomicFile(
-                new File(mStagedLocalesDir,
-                        TextUtils.formatSimple("%s_%d.xml", STAGE_FILE_NAME, userId)));
-        try {
-            stagedDataOutputStream = stageFile.startWrite();
-        } catch (IOException e) {
-            Slog.e(TAG, "Failed to save stage file");
-            return;
-        }
-
-        try {
-            writeToXml(stagedDataOutputStream, stagedData.mPackageStates,  /* forStage= */ true,
-                    stagedData.mCreationTimeMillis);
-            stageFile.finishWrite(stagedDataOutputStream);
-            if (DEBUG) {
-                Slog.d(TAG, "Stage file written.");
-            }
-        } catch (IOException e) {
-            Slog.e(TAG, "Could not write stage file", e);
-            stageFile.failWrite(stagedDataOutputStream);
-        }
-    }
-
     private void deleteStagedDataLocked(@UserIdInt int userId) {
-        AtomicFile stageFile = getStageFileIfExistsLocked(userId);
-        if (stageFile != null) {
-            stageFile.delete();
-        }
         mStagedData.remove(userId);
     }
 
-    private @Nullable AtomicFile getStageFileIfExistsLocked(@UserIdInt int userId) {
-        final File stageFile = new File(mStagedLocalesDir,
-                TextUtils.formatSimple("%s_%d.xml", STAGE_FILE_NAME, userId));
-        return stageFile.isFile() ? new AtomicFile(stageFile)
-                : null;
-    }
-
     /**
      * Parses the backup data from the serialized xml input stream.
      */
@@ -468,15 +346,8 @@
 
     /**
      * Converts the list of app backup data into a serialized xml stream.
-     *
-     * @param forStage Flag to indicate whether this method is called for the purpose of
-     * staging the data. Note that if this is false, {@code creationTimeMillis} is ignored because
-     * we only need it for the stage data.
-     * @param creationTimeMillis The timestamp when the stage data was created. This is required
-     * to determine when to delete the stage data.
      */
-    private static void writeToXml(OutputStream stream,
-            @NonNull HashMap<String, String> pkgStates, boolean forStage, long creationTimeMillis)
+    private static void writeToXml(OutputStream stream, @NonNull HashMap<String, String> pkgStates)
             throws IOException {
         if (pkgStates.isEmpty()) {
             // No need to write anything at all if pkgStates is empty.
@@ -488,11 +359,6 @@
         out.startDocument(/* encoding= */ null, /* standalone= */ true);
         out.startTag(/* namespace= */ null, LOCALES_XML_TAG);
 
-        if (forStage) {
-            out.attribute(/* namespace= */ null, ATTR_CREATION_TIME_MILLIS,
-                    Long.toString(creationTimeMillis));
-        }
-
         for (String pkg : pkgStates.keySet()) {
             out.startTag(/* namespace= */ null, PACKAGE_XML_TAG);
             out.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, pkg);
@@ -504,7 +370,7 @@
         out.endDocument();
     }
 
-    private static class StagedData {
+    static class StagedData {
         final long mCreationTimeMillis;
         final HashMap<String, String> mPackageStates;
 
@@ -517,7 +383,7 @@
     /**
      * Broadcast listener to capture user removed event.
      *
-     * <p>The stage file is deleted when a user is removed.
+     * <p>The stage data is deleted when a user is removed.
      */
     private final class UserMonitor extends BroadcastReceiver {
         @Override
@@ -546,6 +412,8 @@
         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.
@@ -589,7 +457,7 @@
         // Check if the package is installed indeed
         if (!isPackageInstalledForUser(packageName, userId)) {
             Slog.e(TAG, packageName + " not installed for user " + userId
-                    + ". Could not restore locales from stage file");
+                    + ". Could not restore locales from stage data");
             return;
         }
 
@@ -603,8 +471,11 @@
 
                 // Remove the restored entry from the staged data list.
                 stagedData.mPackageStates.remove(pkgName);
-                // Update the file on the disk.
-                writeStageFileLocked(userId);
+
+                // Remove the stage data entry for user if there are no more packages to restore.
+                if (stagedData.mPackageStates.isEmpty()) {
+                    mStagedData.remove(userId);
+                }
 
                 // No need to loop further after restoring locales because the staged data will
                 // contain at most one entry for the newly added package.
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index f84b68e..9b0b782 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -35,6 +35,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
 import android.util.Log;
 import android.util.Pair;
 
@@ -435,7 +436,7 @@
         public void onHostEndpointConnected(HostEndpointInfo info) {
             try {
                 mHub.onHostEndpointConnected(info);
-            } catch (RemoteException e) {
+            } catch (RemoteException | ServiceSpecificException e) {
                 Log.e(TAG, "RemoteException in onHostEndpointConnected");
             }
         }
@@ -444,7 +445,7 @@
         public void onHostEndpointDisconnected(short hostEndpointId) {
             try {
                 mHub.onHostEndpointDisconnected((char) hostEndpointId);
-            } catch (RemoteException e) {
+            } catch (RemoteException | ServiceSpecificException e) {
                 Log.e(TAG, "RemoteException in onHostEndpointDisconnected");
             }
         }
@@ -457,7 +458,7 @@
                 mHub.sendMessageToHub(contextHubId,
                         ContextHubServiceUtil.createAidlContextHubMessage(hostEndpointId, message));
                 return ContextHubTransaction.RESULT_SUCCESS;
-            } catch (RemoteException e) {
+            } catch (RemoteException | ServiceSpecificException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
             }
         }
@@ -470,7 +471,7 @@
             try {
                 mHub.loadNanoapp(contextHubId, aidlNanoAppBinary, transactionId);
                 return ContextHubTransaction.RESULT_SUCCESS;
-            } catch (RemoteException e) {
+            } catch (RemoteException | ServiceSpecificException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
             }
         }
@@ -481,7 +482,7 @@
             try {
                 mHub.unloadNanoapp(contextHubId, nanoappId, transactionId);
                 return ContextHubTransaction.RESULT_SUCCESS;
-            } catch (RemoteException e) {
+            } catch (RemoteException | ServiceSpecificException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
             }
         }
@@ -492,7 +493,7 @@
             try {
                 mHub.enableNanoapp(contextHubId, nanoappId, transactionId);
                 return ContextHubTransaction.RESULT_SUCCESS;
-            } catch (RemoteException e) {
+            } catch (RemoteException | ServiceSpecificException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
             }
         }
@@ -503,7 +504,7 @@
             try {
                 mHub.disableNanoapp(contextHubId, nanoappId, transactionId);
                 return ContextHubTransaction.RESULT_SUCCESS;
-            } catch (RemoteException e) {
+            } catch (RemoteException | ServiceSpecificException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
             }
         }
@@ -513,21 +514,25 @@
             try {
                 mHub.queryNanoapps(contextHubId);
                 return ContextHubTransaction.RESULT_SUCCESS;
-            } catch (RemoteException e) {
+            } catch (RemoteException | ServiceSpecificException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
             }
         }
 
         public void registerCallback(int contextHubId, ICallback callback) throws RemoteException {
             mAidlCallbackMap.put(contextHubId, new ContextHubAidlCallback(contextHubId, callback));
-            mHub.registerCallback(contextHubId, mAidlCallbackMap.get(contextHubId));
+            try {
+                mHub.registerCallback(contextHubId, mAidlCallbackMap.get(contextHubId));
+            } catch (RemoteException | ServiceSpecificException e) {
+                Log.e(TAG, "Exception while registering callback: " + e.getMessage());
+            }
         }
 
         private void onSettingChanged(byte setting, boolean enabled) {
             try {
                 mHub.onSettingChanged(setting, enabled);
-            } catch (RemoteException e) {
-                Log.e(TAG, "RemoteException while sending setting update");
+            } catch (RemoteException | ServiceSpecificException e) {
+                Log.e(TAG, "Exception while sending setting update: " + e.getMessage());
             }
         }
     }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index fab11a1..682a27a 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -39,7 +39,10 @@
 import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE;
 import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled;
 import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential;
+import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_STRONG;
+import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_WEAK;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -123,6 +126,8 @@
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.ICheckCredentialProgressCallback;
 import com.android.internal.widget.ILockSettings;
+import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
+import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
 import com.android.internal.widget.LockscreenCredential;
@@ -135,6 +140,7 @@
 import com.android.server.locksettings.LockSettingsStorage.PersistentData;
 import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
 import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
+import com.android.server.locksettings.SyntheticPasswordManager.TokenType;
 import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
@@ -1123,6 +1129,16 @@
         return mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED;
     }
 
+    private void checkManageWeakEscrowTokenMethodUsage() {
+        mContext.enforceCallingOrSelfPermission(
+                Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN,
+                "Requires MANAGE_WEAK_ESCROW_TOKEN permission.");
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+            throw new IllegalArgumentException(
+                    "Weak escrow token are only for automotive devices.");
+        }
+    }
+
     @Override
     public boolean hasSecureLockScreen() {
         return mHasSecureLockScreen;
@@ -1911,6 +1927,97 @@
         });
     }
 
+    /** Register the given WeakEscrowTokenRemovedListener. */
+    @Override
+    public boolean registerWeakEscrowTokenRemovedListener(
+            @NonNull IWeakEscrowTokenRemovedListener listener) {
+        checkManageWeakEscrowTokenMethodUsage();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return mSpManager.registerWeakEscrowTokenRemovedListener(listener);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /** Unregister the given WeakEscrowTokenRemovedListener. */
+    @Override
+    public boolean unregisterWeakEscrowTokenRemovedListener(
+            @NonNull IWeakEscrowTokenRemovedListener listener) {
+        checkManageWeakEscrowTokenMethodUsage();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return mSpManager.unregisterWeakEscrowTokenRemovedListener(listener);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public long addWeakEscrowToken(byte[] token, int userId,
+            @NonNull IWeakEscrowTokenActivatedListener listener) {
+        checkManageWeakEscrowTokenMethodUsage();
+        Objects.requireNonNull(listener, "Listener can not be null.");
+        EscrowTokenStateChangeCallback internalListener = (handle, userId1) -> {
+            try {
+                listener.onWeakEscrowTokenActivated(handle, userId1);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Exception while notifying weak escrow token has been activated", e);
+            }
+        };
+        final long restoreToken = Binder.clearCallingIdentity();
+        try {
+            return addEscrowToken(token, TOKEN_TYPE_WEAK, userId, internalListener);
+        } finally {
+            Binder.restoreCallingIdentity(restoreToken);
+        }
+    }
+
+    @Override
+    public boolean removeWeakEscrowToken(long handle, int userId) {
+        checkManageWeakEscrowTokenMethodUsage();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return removeEscrowToken(handle, userId);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public boolean isWeakEscrowTokenActive(long handle, int userId) {
+        checkManageWeakEscrowTokenMethodUsage();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return isEscrowTokenActive(handle, userId);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public boolean isWeakEscrowTokenValid(long handle, byte[] token, int userId) {
+        checkManageWeakEscrowTokenMethodUsage();
+        final long restoreToken = Binder.clearCallingIdentity();
+        try {
+            synchronized (mSpManager) {
+                if (!mSpManager.hasEscrowData(userId)) {
+                    Slog.w(TAG, "Escrow token is disabled on the current user");
+                    return false;
+                }
+                AuthenticationResult authResult = mSpManager.unwrapWeakTokenBasedSyntheticPassword(
+                        getGateKeeperService(), handle, token, userId);
+                if (authResult.authToken == null) {
+                    Slog.w(TAG, "Invalid escrow token supplied");
+                    return false;
+                }
+                return true;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(restoreToken);
+        }
+    }
+
     @VisibleForTesting /** Note: this method is overridden in unit tests */
     protected void tieProfileLockToParent(int userId, LockscreenCredential password) {
         if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
@@ -3029,6 +3136,7 @@
     private long setLockCredentialWithAuthTokenLocked(LockscreenCredential credential,
             AuthenticationToken auth, int userId) {
         if (DEBUG) Slog.d(TAG, "setLockCredentialWithAuthTokenLocked: user=" + userId);
+        final int savedCredentialType = getCredentialTypeInternal(userId);
         long newHandle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(),
                 credential, auth, userId);
         final Map<Integer, LockscreenCredential> profilePasswords;
@@ -3075,6 +3183,9 @@
 
         setUserPasswordMetrics(credential, userId);
         mManagedProfilePasswordCache.removePassword(userId);
+        if (savedCredentialType != CREDENTIAL_TYPE_NONE) {
+            mSpManager.destroyAllWeakTokenBasedSyntheticPasswords(userId);
+        }
 
         if (profilePasswords != null) {
             for (Map.Entry<Integer, LockscreenCredential> entry : profilePasswords.entrySet()) {
@@ -3242,8 +3353,9 @@
         }
     }
 
-    private long addEscrowToken(byte[] token, int userId, EscrowTokenStateChangeCallback callback) {
-        if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId);
+    private long addEscrowToken(@NonNull byte[] token, @TokenType int type, int userId,
+            @NonNull EscrowTokenStateChangeCallback callback) {
+        if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId + ", type=" + type);
         synchronized (mSpManager) {
             // Migrate to synthetic password based credentials if the user has no password,
             // the token can then be activated immediately.
@@ -3264,7 +3376,8 @@
                     throw new SecurityException("Escrow token is disabled on the current user");
                 }
             }
-            long handle = mSpManager.createTokenBasedSyntheticPassword(token, userId, callback);
+            long handle = mSpManager.createTokenBasedSyntheticPassword(token, type, userId,
+                    callback);
             if (auth != null) {
                 mSpManager.activateTokenBasedSyntheticPassword(handle, auth, userId);
             }
@@ -3345,8 +3458,8 @@
     private boolean setLockCredentialWithTokenInternalLocked(LockscreenCredential credential,
             long tokenHandle, byte[] token, int userId) {
         final AuthenticationResult result;
-        result = mSpManager.unwrapTokenBasedSyntheticPassword(
-                getGateKeeperService(), tokenHandle, token, userId);
+        result = mSpManager.unwrapTokenBasedSyntheticPassword(getGateKeeperService(), tokenHandle,
+                token, userId);
         if (result.authToken == null) {
             Slog.w(TAG, "Invalid escrow token supplied");
             return false;
@@ -3369,7 +3482,8 @@
         AuthenticationResult authResult;
         synchronized (mSpManager) {
             if (!mSpManager.hasEscrowData(userId)) {
-                throw new SecurityException("Escrow token is disabled on the current user");
+                Slog.w(TAG, "Escrow token is disabled on the current user");
+                return false;
             }
             authResult = mSpManager.unwrapTokenBasedSyntheticPassword(getGateKeeperService(),
                     tokenHandle, token, userId);
@@ -3643,7 +3757,8 @@
         @Override
         public long addEscrowToken(byte[] token, int userId,
                 EscrowTokenStateChangeCallback callback) {
-            return LockSettingsService.this.addEscrowToken(token, userId, callback);
+            return LockSettingsService.this.addEscrowToken(token, TOKEN_TYPE_STRONG, userId,
+                    callback);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 601a572..2da4431 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.admin.PasswordMetrics;
@@ -28,6 +29,7 @@
 import android.hardware.weaver.V1_0.WeaverReadResponse;
 import android.hardware.weaver.V1_0.WeaverReadStatus;
 import android.hardware.weaver.V1_0.WeaverStatus;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.UserManager;
 import android.security.GateKeeper;
@@ -41,6 +43,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.widget.ICheckCredentialProgressCallback;
+import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.VerifyCredentialResponse;
@@ -48,6 +51,8 @@
 
 import libcore.util.HexEncoding;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.nio.ByteBuffer;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
@@ -111,7 +116,8 @@
     private static final byte SYNTHETIC_PASSWORD_VERSION_V2 = 2;
     private static final byte SYNTHETIC_PASSWORD_VERSION_V3 = 3;
     private static final byte SYNTHETIC_PASSWORD_PASSWORD_BASED = 0;
-    private static final byte SYNTHETIC_PASSWORD_TOKEN_BASED = 1;
+    private static final byte SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED = 1;
+    private static final byte SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED = 2;
 
     // 256-bit synthetic password
     private static final byte SYNTHETIC_PASSWORD_LENGTH = 256 / 8;
@@ -366,10 +372,47 @@
         }
     }
 
+    static class SyntheticPasswordBlob {
+        byte mVersion;
+        byte mType;
+        byte[] mContent;
+
+        public static SyntheticPasswordBlob create(byte version, byte type, byte[] content) {
+            SyntheticPasswordBlob result = new SyntheticPasswordBlob();
+            result.mVersion = version;
+            result.mType = type;
+            result.mContent = content;
+            return result;
+        }
+
+        public static SyntheticPasswordBlob fromBytes(byte[] data) {
+            SyntheticPasswordBlob result = new SyntheticPasswordBlob();
+            result.mVersion = data[0];
+            result.mType = data[1];
+            result.mContent = Arrays.copyOfRange(data, 2, data.length);
+            return result;
+        }
+
+        public byte[] toByte() {
+            byte[] blob = new byte[mContent.length + 1 + 1];
+            blob[0] = mVersion;
+            blob[1] = mType;
+            System.arraycopy(mContent, 0, blob, 2, mContent.length);
+            return blob;
+        }
+    }
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({TOKEN_TYPE_STRONG, TOKEN_TYPE_WEAK})
+    @interface TokenType {}
+    static final int TOKEN_TYPE_STRONG = 0;
+    static final int TOKEN_TYPE_WEAK = 1;
+
     static class TokenData {
         byte[] secdiscardableOnDisk;
         byte[] weaverSecret;
         byte[] aggregatedSecret;
+        @TokenType int mType;
         EscrowTokenStateChangeCallback mCallback;
     }
 
@@ -381,6 +424,9 @@
 
     private final UserManager mUserManager;
 
+    private final RemoteCallbackList<IWeakEscrowTokenRemovedListener> mListeners =
+            new RemoteCallbackList<>();
+
     public SyntheticPasswordManager(Context context, LockSettingsStorage storage,
             UserManager userManager, PasswordSlotManager passwordSlotManager) {
         mContext = context;
@@ -880,13 +926,34 @@
      * Create a token based Synthetic password for the given user.
      * @return the handle of the token
      */
-    public long createTokenBasedSyntheticPassword(byte[] token, int userId,
+    public long createStrongTokenBasedSyntheticPassword(byte[] token, int userId,
+            @Nullable EscrowTokenStateChangeCallback changeCallback) {
+        return createTokenBasedSyntheticPassword(token, TOKEN_TYPE_STRONG, userId,
+                changeCallback);
+    }
+
+    /**
+     * Create a weak token based Synthetic password for the given user.
+     * @return the handle of the weak token
+     */
+    public long createWeakTokenBasedSyntheticPassword(byte[] token, int userId,
+            @Nullable EscrowTokenStateChangeCallback changeCallback) {
+        return createTokenBasedSyntheticPassword(token, TOKEN_TYPE_WEAK, userId,
+                changeCallback);
+    }
+
+    /**
+     * Create a token based Synthetic password of the given type for the given user.
+     * @return the handle of the token
+     */
+    public long createTokenBasedSyntheticPassword(byte[] token, @TokenType int type, int userId,
             @Nullable EscrowTokenStateChangeCallback changeCallback) {
         long handle = generateHandle();
         if (!tokenMap.containsKey(userId)) {
             tokenMap.put(userId, new ArrayMap<>());
         }
         TokenData tokenData = new TokenData();
+        tokenData.mType = type;
         final byte[] secdiscardable = secureRandom(SECDISCARDABLE_LENGTH);
         if (isWeaverAvailable()) {
             tokenData.weaverSecret = secureRandom(mWeaverConfig.valueSize);
@@ -910,6 +977,7 @@
         return new ArraySet<>(tokenMap.get(userId).keySet());
     }
 
+    /** Remove the given pending token. */
     public boolean removePendingToken(long handle, int userId) {
         if (!tokenMap.containsKey(userId)) {
             return false;
@@ -941,7 +1009,7 @@
             mPasswordSlotManager.markSlotInUse(slot);
         }
         saveSecdiscardable(handle, tokenData.secdiscardableOnDisk, userId);
-        createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED, authToken,
+        createSyntheticPasswordBlob(handle, getTokenBasedBlobType(tokenData.mType), authToken,
                 tokenData.aggregatedSecret, 0L, userId);
         tokenMap.get(userId).remove(handle);
         if (tokenData.mCallback != null) {
@@ -953,26 +1021,23 @@
     private void createSyntheticPasswordBlob(long handle, byte type, AuthenticationToken authToken,
             byte[] applicationId, long sid, int userId) {
         final byte[] secret;
-        if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
+        if (type == SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED
+                || type == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) {
             secret = authToken.getEscrowSecret();
         } else {
             secret = authToken.getSyntheticPassword();
         }
         byte[] content = createSPBlob(getKeyName(handle), secret, applicationId, sid);
-        byte[] blob = new byte[content.length + 1 + 1];
         /*
          * We can upgrade from v1 to v2 because that's just a change in the way that
          * the SP is stored. However, we can't upgrade to v3 because that is a change
          * in the way that passwords are derived from the SP.
          */
-        if (authToken.mVersion == SYNTHETIC_PASSWORD_VERSION_V3) {
-            blob[0] = SYNTHETIC_PASSWORD_VERSION_V3;
-        } else {
-            blob[0] = SYNTHETIC_PASSWORD_VERSION_V2;
-        }
-        blob[1] = type;
-        System.arraycopy(content, 0, blob, 2, content.length);
-        saveState(SP_BLOB_NAME, blob, handle, userId);
+        byte version = authToken.mVersion == SYNTHETIC_PASSWORD_VERSION_V3
+                ? SYNTHETIC_PASSWORD_VERSION_V3 : SYNTHETIC_PASSWORD_VERSION_V2;
+
+        SyntheticPasswordBlob blob = SyntheticPasswordBlob.create(version, type, content);
+        saveState(SP_BLOB_NAME, blob.toByte(), handle, userId);
     }
 
     /**
@@ -1089,6 +1154,36 @@
      */
     public @NonNull AuthenticationResult unwrapTokenBasedSyntheticPassword(
             IGateKeeperService gatekeeper, long handle, byte[] token, int userId) {
+        SyntheticPasswordBlob blob = SyntheticPasswordBlob
+                .fromBytes(loadState(SP_BLOB_NAME, handle, userId));
+        return unwrapTokenBasedSyntheticPasswordInternal(gatekeeper, handle,
+                blob.mType, token, userId);
+    }
+
+    /**
+     * Decrypt a synthetic password by supplying an strong escrow token and corresponding token
+     * blob handle generated previously. If the decryption is successful, initiate a GateKeeper
+     * verification to referesh the SID & Auth token maintained by the system.
+     */
+    public @NonNull AuthenticationResult unwrapStrongTokenBasedSyntheticPassword(
+            IGateKeeperService gatekeeper, long handle, byte[] token, int userId) {
+        return unwrapTokenBasedSyntheticPasswordInternal(gatekeeper, handle,
+                SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED, token, userId);
+    }
+
+    /**
+     * Decrypt a synthetic password by supplying a weak escrow token and corresponding token
+     * blob handle generated previously. If the decryption is successful, initiate a GateKeeper
+     * verification to referesh the SID & Auth token maintained by the system.
+     */
+    public @NonNull AuthenticationResult unwrapWeakTokenBasedSyntheticPassword(
+            IGateKeeperService gatekeeper, long handle, byte[] token, int userId) {
+        return unwrapTokenBasedSyntheticPasswordInternal(gatekeeper, handle,
+                SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED, token, userId);
+    }
+
+    private  @NonNull AuthenticationResult unwrapTokenBasedSyntheticPasswordInternal(
+            IGateKeeperService gatekeeper, long handle, byte type, byte[] token, int userId) {
         AuthenticationResult result = new AuthenticationResult();
         byte[] secdiscardable = loadSecdiscardable(handle, userId);
         int slotId = loadWeaverSlot(handle, userId);
@@ -1109,8 +1204,7 @@
                     PERSONALISATION_WEAVER_TOKEN, secdiscardable);
         }
         byte[] applicationId = transformUnderSecdiscardable(token, secdiscardable);
-        result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED,
-                applicationId, 0L, userId);
+        result.authToken = unwrapSyntheticPasswordBlob(handle, type, applicationId, 0L, userId);
         if (result.authToken != null) {
             result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);
             if (result.gkResponse == null) {
@@ -1126,33 +1220,33 @@
 
     private AuthenticationToken unwrapSyntheticPasswordBlob(long handle, byte type,
             byte[] applicationId, long sid, int userId) {
-        byte[] blob = loadState(SP_BLOB_NAME, handle, userId);
-        if (blob == null) {
+        byte[] data = loadState(SP_BLOB_NAME, handle, userId);
+        if (data == null) {
             return null;
         }
-        final byte version = blob[0];
-        if (version != SYNTHETIC_PASSWORD_VERSION_V3
-                && version != SYNTHETIC_PASSWORD_VERSION_V2
-                && version != SYNTHETIC_PASSWORD_VERSION_V1) {
+        SyntheticPasswordBlob blob = SyntheticPasswordBlob.fromBytes(data);
+        if (blob.mVersion != SYNTHETIC_PASSWORD_VERSION_V3
+                && blob.mVersion != SYNTHETIC_PASSWORD_VERSION_V2
+                && blob.mVersion != SYNTHETIC_PASSWORD_VERSION_V1) {
             throw new IllegalArgumentException("Unknown blob version");
         }
-        if (blob[1] != type) {
+        if (blob.mType != type) {
             throw new IllegalArgumentException("Invalid blob type");
         }
         final byte[] secret;
-        if (version == SYNTHETIC_PASSWORD_VERSION_V1) {
-            secret = SyntheticPasswordCrypto.decryptBlobV1(getKeyName(handle),
-                    Arrays.copyOfRange(blob, 2, blob.length), applicationId);
+        if (blob.mVersion == SYNTHETIC_PASSWORD_VERSION_V1) {
+            secret = SyntheticPasswordCrypto.decryptBlobV1(getKeyName(handle), blob.mContent,
+                    applicationId);
         } else {
-            secret = decryptSPBlob(getKeyName(handle),
-                Arrays.copyOfRange(blob, 2, blob.length), applicationId);
+            secret = decryptSPBlob(getKeyName(handle), blob.mContent, applicationId);
         }
         if (secret == null) {
             Slog.e(TAG, "Fail to decrypt SP for user " + userId);
             return null;
         }
-        AuthenticationToken result = new AuthenticationToken(version);
-        if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
+        AuthenticationToken result = new AuthenticationToken(blob.mVersion);
+        if (type == SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED
+                || type == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) {
             if (!loadEscrowData(result, userId)) {
                 Slog.e(TAG, "User is not escrowable: " + userId);
                 return null;
@@ -1161,7 +1255,7 @@
         } else {
             result.recreateDirectly(secret);
         }
-        if (version == SYNTHETIC_PASSWORD_VERSION_V1) {
+        if (blob.mVersion == SYNTHETIC_PASSWORD_VERSION_V1) {
             Slog.i(TAG, "Upgrade v1 SP blob for user " + userId + ", type = " + type);
             createSyntheticPasswordBlob(handle, type, result, applicationId, sid, userId);
         }
@@ -1233,9 +1327,28 @@
         return hasState(SP_BLOB_NAME, handle, userId);
     }
 
+    /** Destroy the escrow token with the given handle for the given user. */
     public void destroyTokenBasedSyntheticPassword(long handle, int userId) {
+        SyntheticPasswordBlob blob = SyntheticPasswordBlob.fromBytes(loadState(SP_BLOB_NAME, handle,
+                userId));
         destroySyntheticPassword(handle, userId);
         destroyState(SECDISCARDABLE_NAME, handle, userId);
+        if (blob.mType == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) {
+            notifyWeakEscrowTokenRemovedListeners(handle, userId);
+        }
+    }
+
+    /** Destroy all weak escrow tokens for the given user. */
+    public void destroyAllWeakTokenBasedSyntheticPasswords(int userId) {
+        List<Long> handles = mStorage.listSyntheticPasswordHandlesForUser(SECDISCARDABLE_NAME,
+                userId);
+        for (long handle: handles) {
+            SyntheticPasswordBlob blob = SyntheticPasswordBlob.fromBytes(loadState(SP_BLOB_NAME,
+                    handle, userId));
+            if (blob.mType == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) {
+                destroyTokenBasedSyntheticPassword(handle, userId);
+            }
+        }
     }
 
     public void destroyPasswordBasedSyntheticPassword(long handle, int userId) {
@@ -1285,6 +1398,16 @@
         return loadState(SECDISCARDABLE_NAME, handle, userId);
     }
 
+    private byte getTokenBasedBlobType(@TokenType int type) {
+        switch (type) {
+            case TOKEN_TYPE_WEAK:
+                return SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED;
+            case TOKEN_TYPE_STRONG:
+            default:
+                return SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED;
+        }
+    }
+
     /**
      * Retrieves the saved password metrics associated with a SP handle. Only meaningful to be
      * called on the handle of a password-based synthetic password. A valid AuthenticationToken for
@@ -1439,4 +1562,33 @@
         }
         return success;
     }
+
+    /** Register the given IWeakEscrowTokenRemovedListener. */
+    public boolean registerWeakEscrowTokenRemovedListener(
+            IWeakEscrowTokenRemovedListener listener) {
+        return mListeners.register(listener);
+    }
+
+    /** Unregister the given IWeakEscrowTokenRemovedListener. */
+    public boolean unregisterWeakEscrowTokenRemovedListener(
+            IWeakEscrowTokenRemovedListener listener) {
+        return mListeners.unregister(listener);
+    }
+
+    private void notifyWeakEscrowTokenRemovedListeners(long handle, int userId) {
+        int i = mListeners.beginBroadcast();
+        try {
+            while (i > 0) {
+                i--;
+                try {
+                    mListeners.getBroadcastItem(i).onWeakEscrowTokenRemoved(handle, userId);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Exception while notifying WeakEscrowTokenRemovedListener.",
+                            e);
+                }
+            }
+        } finally {
+            mListeners.finishBroadcast();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 4822d6a..96391ac 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -52,7 +52,6 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.SystemClock;
-import android.text.TextUtils;
 import android.util.Log;
 import android.view.KeyEvent;
 
@@ -643,22 +642,24 @@
     }
 
     private void pushQueueUpdate() {
-        ParceledListSlice<QueueItem> parcelableQueue;
+        ArrayList<QueueItem> toSend;
         synchronized (mLock) {
             if (mDestroyed) {
                 return;
             }
-            if (mQueue == null) {
-                parcelableQueue = null;
-            } else {
-                parcelableQueue = new ParceledListSlice<>(mQueue);
-                // Limit the size of initial Parcel to prevent binder buffer overflow
-                // as onQueueChanged is an async binder call.
-                parcelableQueue.setInlineCountLimit(1);
+            toSend = new ArrayList<>();
+            if (mQueue != null) {
+                toSend.ensureCapacity(mQueue.size());
+                toSend.addAll(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);
+
             try {
                 holder.mCallback.onQueueChanged(parcelableQueue);
             } catch (DeadObjectException e) {
diff --git a/services/core/java/com/android/server/media/OWNERS b/services/core/java/com/android/server/media/OWNERS
index 2e2d812..8097f4e 100644
--- a/services/core/java/com/android/server/media/OWNERS
+++ b/services/core/java/com/android/server/media/OWNERS
@@ -1,8 +1,6 @@
+# Bug component: 137631
 elaurent@google.com
-hdmoon@google.com
-insun@google.com
-jaewan@google.com
-jinpark@google.com
-klhyun@google.com
 lajos@google.com
-sungsoo@google.com
+
+# go/android-fwk-media-solutions for info on areas of ownership.
+include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 65c5b88..5660951 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -130,7 +130,7 @@
 import static com.android.internal.util.XmlUtils.writeIntAttribute;
 import static com.android.internal.util.XmlUtils.writeLongAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
-import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
+import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT;
 import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
 
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index 5455738..c548e7e 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -39,4 +39,7 @@
 
     /** Get the number of notification channels for a given package */
     int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
+
+    /** Does the specified package/uid have permission to post notifications? */
+    boolean areNotificationsEnabledForPackage(String pkg, int uid);
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a0eeb65..a5edfed 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -6205,6 +6205,11 @@
             return NotificationManagerService.this
                     .getNumNotificationChannelsForPackage(pkg, uid, includeDeleted);
         }
+
+        @Override
+        public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
+            return areNotificationsEnabledForPackageInt(pkg, uid);
+        }
     };
 
     int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted) {
@@ -6669,31 +6674,33 @@
         // package or a registered listener can enqueue.  Prevents DOS attacks and deals with leaks.
         if (!isSystemNotification && !isNotificationFromListener) {
             final int callingUid = Binder.getCallingUid();
-            if (mNotificationsByKey.get(r.getSbn().getKey()) == null
-                    && isCallerInstantApp(callingUid, userId)) {
-                // Ephemeral apps have some special constraints for notifications.
-                // They are not allowed to create new notifications however they are allowed to
-                // update notifications created by the system (e.g. a foreground service
-                // notification).
-                throw new SecurityException("Instant app " + pkg
-                        + " cannot create notifications");
-            }
+            synchronized (mNotificationLock) {
+                if (mNotificationsByKey.get(r.getSbn().getKey()) == null
+                        && isCallerInstantApp(callingUid, userId)) {
+                    // Ephemeral apps have some special constraints for notifications.
+                    // They are not allowed to create new notifications however they are allowed to
+                    // update notifications created by the system (e.g. a foreground service
+                    // notification).
+                    throw new SecurityException("Instant app " + pkg
+                            + " cannot create notifications");
+                }
 
-            // rate limit updates that aren't completed progress notifications
-            if (mNotificationsByKey.get(r.getSbn().getKey()) != null
-                    && !r.getNotification().hasCompletedProgress()
-                    && !isAutogroup) {
+                // rate limit updates that aren't completed progress notifications
+                if (mNotificationsByKey.get(r.getSbn().getKey()) != null
+                        && !r.getNotification().hasCompletedProgress()
+                        && !isAutogroup) {
 
-                final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
-                if (appEnqueueRate > mMaxPackageEnqueueRate) {
-                    mUsageStats.registerOverRateQuota(pkg);
-                    final long now = SystemClock.elapsedRealtime();
-                    if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {
-                        Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate
-                                + ". Shedding " + r.getSbn().getKey() + ". package=" + pkg);
-                        mLastOverRateLogTime = now;
+                    final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
+                    if (appEnqueueRate > mMaxPackageEnqueueRate) {
+                        mUsageStats.registerOverRateQuota(pkg);
+                        final long now = SystemClock.elapsedRealtime();
+                        if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {
+                            Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate
+                                    + ". Shedding " + r.getSbn().getKey() + ". package=" + pkg);
+                            mLastOverRateLogTime = now;
+                        }
+                        return false;
                     }
-                    return false;
                 }
             }
 
diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java
index 8acc857..54dd113 100644
--- a/services/core/java/com/android/server/notification/VibratorHelper.java
+++ b/services/core/java/com/android/server/notification/VibratorHelper.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.media.AudioAttributes;
 import android.os.Process;
 import android.os.VibrationAttributes;
@@ -39,18 +40,16 @@
 
     private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
     private static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps
-    private static final int CHIRP_LEVEL_DURATION_MILLIS = 100;
-    private static final int DEFAULT_CHIRP_RAMP_DURATION_MILLIS = 100;
-    private static final int FALLBACK_CHIRP_RAMP_DURATION_MILLIS = 50;
 
     private final Vibrator mVibrator;
     private final long[] mDefaultPattern;
     private final long[] mFallbackPattern;
+    @Nullable private final float[] mDefaultPwlePattern;
+    @Nullable private final float[] mFallbackPwlePattern;
 
     public VibratorHelper(Context context) {
         mVibrator = context.getSystemService(Vibrator.class);
-        mDefaultPattern = getLongArray(
-                context.getResources(),
+        mDefaultPattern = getLongArray(context.getResources(),
                 com.android.internal.R.array.config_defaultNotificationVibePattern,
                 VIBRATE_PATTERN_MAXLEN,
                 DEFAULT_VIBRATE_PATTERN);
@@ -58,6 +57,10 @@
                 R.array.config_notificationFallbackVibePattern,
                 VIBRATE_PATTERN_MAXLEN,
                 DEFAULT_VIBRATE_PATTERN);
+        mDefaultPwlePattern = getFloatArray(context.getResources(),
+                com.android.internal.R.array.config_defaultNotificationVibeWaveform);
+        mFallbackPwlePattern = getFloatArray(context.getResources(),
+                com.android.internal.R.array.config_notificationFallbackVibeWaveform);
     }
 
     /**
@@ -83,6 +86,52 @@
     }
 
     /**
+     * Safely create a {@link VibrationEffect} from given waveform description.
+     *
+     * <p>The waveform is described by a sequence of values for target amplitude, frequency and
+     * duration, that are forwarded to
+     * {@link VibrationEffect.WaveformBuilder#addRamp(float, float, int)}.
+     *
+     * <p>This method returns {@code null} if the pattern is also {@code null} or invalid.
+     *
+     * @param values The list of values describing the waveform as a sequence of target amplitude,
+     *               frequency and duration.
+     * @param insistent {@code true} if the vibration should loop until it is cancelled.
+     */
+    @Nullable
+    public static VibrationEffect createPwleWaveformVibration(@Nullable float[] values,
+            boolean insistent) {
+        try {
+            if (values == null) {
+                return null;
+            }
+
+            int length = values.length;
+            // The waveform is described by triples (amplitude, frequency, duration)
+            if ((length == 0) || (length % 3 != 0)) {
+                return null;
+            }
+
+            VibrationEffect.WaveformBuilder waveformBuilder = VibrationEffect.startWaveform();
+            for (int i = 0; i < length; i += 3) {
+                waveformBuilder.addRamp(
+                        /* amplitude= */ values[i],
+                        /* frequencyHz= */ values[i + 1],
+                        /* duration= */ (int) values[i + 2]);
+            }
+
+            if (insistent) {
+                return waveformBuilder.build(/* repeat= */ 0);
+            }
+            return waveformBuilder.build();
+        } catch (IllegalArgumentException e) {
+            Slog.e(TAG, "Error creating vibration PWLE waveform with pattern: "
+                    + Arrays.toString(values));
+        }
+        return null;
+    }
+
+    /**
      * Vibrate the device with given {@code effect}.
      *
      * <p>We need to vibrate as "android" so we can breakthrough DND.
@@ -106,7 +155,10 @@
      */
     public VibrationEffect createFallbackVibration(boolean insistent) {
         if (mVibrator.hasFrequencyControl()) {
-            return createChirpVibration(FALLBACK_CHIRP_RAMP_DURATION_MILLIS, insistent);
+            VibrationEffect effect = createPwleWaveformVibration(mFallbackPwlePattern, insistent);
+            if (effect != null) {
+                return effect;
+            }
         }
         return createWaveformVibration(mFallbackPattern, insistent);
     }
@@ -118,29 +170,29 @@
      */
     public VibrationEffect createDefaultVibration(boolean insistent) {
         if (mVibrator.hasFrequencyControl()) {
-            return createChirpVibration(DEFAULT_CHIRP_RAMP_DURATION_MILLIS, insistent);
+            VibrationEffect effect = createPwleWaveformVibration(mDefaultPwlePattern, insistent);
+            if (effect != null) {
+                return effect;
+            }
         }
         return createWaveformVibration(mDefaultPattern, insistent);
     }
 
-    private static VibrationEffect createChirpVibration(int rampDuration, boolean insistent) {
-        VibrationEffect.WaveformBuilder waveformBuilder = VibrationEffect.startWaveform()
-                .addStep(/* amplitude= */ 0, /* frequencyHz= */ 60f, /* duration= */ 0)
-                .addRamp(/* amplitude= */ 1, /* frequencyHz= */ 120f, rampDuration)
-                .addStep(/* amplitude= */ 1, /* frequencyHz= */ 120f, CHIRP_LEVEL_DURATION_MILLIS)
-                .addRamp(/* amplitude= */ 0, /* frequencyHz= */ 60f, rampDuration);
-
-        if (insistent) {
-            return waveformBuilder
-                    .addStep(/* amplitude= */ 0, CHIRP_LEVEL_DURATION_MILLIS)
-                    .build(/* repeat= */ 0);
+    @Nullable
+    private static float[] getFloatArray(Resources resources, int resId) {
+        TypedArray array = resources.obtainTypedArray(resId);
+        try {
+            float[] values = new float[array.length()];
+            for (int i = 0; i < values.length; i++) {
+                values[i] = array.getFloat(i, Float.NaN);
+                if (Float.isNaN(values[i])) {
+                    return null;
+                }
+            }
+            return values;
+        } finally {
+            array.recycle();
         }
-
-        VibrationEffect singleBeat = waveformBuilder.build();
-        return VibrationEffect.startComposition()
-                .addEffect(singleBeat)
-                .addEffect(singleBeat, /* delay= */ CHIRP_LEVEL_DURATION_MILLIS)
-                .compose();
     }
 
     private static long[] getLongArray(Resources resources, int resId, int maxLength, long[] def) {
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 0556748..c285e27 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -799,7 +799,7 @@
                 throw new RuntimeException(re);
             } catch (Exception e) {
                 throw new PackageManagerException(
-                        PackageInstaller.SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                        PackageInstaller.SessionInfo.SESSION_VERIFICATION_FAILED,
                         "apexd verification failed : " + e.getMessage());
             }
         }
@@ -826,7 +826,7 @@
                 throw new RuntimeException(re);
             } catch (Exception e) {
                 throw new PackageManagerException(
-                        PackageInstaller.SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                        PackageInstaller.SessionInfo.SESSION_VERIFICATION_FAILED,
                         "Failed to mark apexd session as ready : " + e.getMessage());
             }
         }
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 05a51cc..31df0a5 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -896,10 +896,10 @@
         synchronized (mLock) {
             if (!mFinishedPostBootUpdate) {
                 mFinishedPostBootUpdate = true;
-                JobScheduler js = mInjector.getJobScheduler();
-                js.cancel(JOB_POST_BOOT_UPDATE);
             }
         }
+        // Safe to do this outside lock.
+        mInjector.getJobScheduler().cancel(JOB_POST_BOOT_UPDATE);
     }
 
     private void notifyPinService(ArraySet<String> updatedPackages) {
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index 62db886..b307984 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.Manifest.permission.MANAGE_APP_OPS_MODES;
+import static android.Manifest.permission.START_CROSS_PROFILE_ACTIVITIES;
 import static android.app.AppOpsManager.OP_INTERACT_ACROSS_PROFILES;
 import static android.content.Intent.FLAG_RECEIVER_REGISTERED_ONLY;
 import static android.content.pm.CrossProfileApps.ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED;
@@ -154,17 +155,15 @@
             // must have the required permission and the users must be in the same profile group
             // in order to launch any of its own activities.
             if (callerUserId != userId) {
-                final int permissionFlag = PermissionChecker.checkPermissionForPreflight(
-                        mContext,
-                        INTERACT_ACROSS_PROFILES,
-                        callingPid,
-                        callingUid,
-                        callingPackage);
-                if (permissionFlag != PermissionChecker.PERMISSION_GRANTED
-                        || !isSameProfileGroup(callerUserId, userId)) {
-                    throw new SecurityException("Attempt to launch activity without required "
-                            + INTERACT_ACROSS_PROFILES
-                            + " permission or target user is not in the same profile group.");
+                if (!hasInteractAcrossProfilesPermission(callingPackage, callingUid, callingPid)
+                        && !isPermissionGranted(START_CROSS_PROFILE_ACTIVITIES, callingUid)) {
+                    throw new SecurityException("Attempt to launch activity without one of the"
+                            + " required " + INTERACT_ACROSS_PROFILES + " or "
+                            + START_CROSS_PROFILE_ACTIVITIES + " permissions.");
+                }
+                if (!isSameProfileGroup(callerUserId, userId)) {
+                    throw new SecurityException("Attempt to launch activity when target user is"
+                            + " not in the same profile group.");
                 }
             }
             launchIntent.setComponent(component);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 9302aad..14c0761 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1969,14 +1969,15 @@
                             reconciledPkg.mPrepareResult.mExistingPackage.getPackageName());
                     if ((reconciledPkg.mInstallArgs.mInstallFlags & PackageManager.DONT_KILL_APP)
                             == 0) {
-                        if (ps1.getOldCodePaths() == null) {
-                            ps1.setOldCodePaths(new ArraySet<>());
+                        Set<String> oldCodePaths = ps1.getOldCodePaths();
+                        if (oldCodePaths == null) {
+                            oldCodePaths = new ArraySet<>();
                         }
-                        Collections.addAll(ps1.getOldCodePaths(), oldPackage.getBaseApkPath());
+                        Collections.addAll(oldCodePaths, oldPackage.getBaseApkPath());
                         if (oldPackage.getSplitCodePaths() != null) {
-                            Collections.addAll(ps1.getOldCodePaths(),
-                                    oldPackage.getSplitCodePaths());
+                            Collections.addAll(oldCodePaths, oldPackage.getSplitCodePaths());
                         }
+                        ps1.setOldCodePaths(oldCodePaths);
                     } else {
                         ps1.setOldCodePaths(null);
                     }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 4d6b3c2..8ebd254 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -70,6 +70,7 @@
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.ExceptionUtils;
+import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -121,7 +122,7 @@
 public class PackageInstallerService extends IPackageInstaller.Stub implements
         PackageSessionProvider {
     private static final String TAG = "PackageInstaller";
-    private static final boolean LOGD = false;
+    private static final boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final boolean DEBUG = Build.IS_DEBUGGABLE;
 
@@ -330,7 +331,7 @@
                 StagingManager.StagedSession stagedSession = session.mStagedSession;
                 if (!stagedSession.isInTerminalState() && stagedSession.hasParentSessionId()
                         && getSession(stagedSession.getParentSessionId()) == null) {
-                    stagedSession.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+                    stagedSession.setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED,
                             "An orphan staged session " + stagedSession.sessionId() + " is found, "
                                 + "parent " + stagedSession.getParentSessionId() + " is missing");
                     continue;
@@ -510,6 +511,7 @@
                 valid = true;
             }
             if (!valid) {
+                Slog.w(TAG, "Remove old session: " + session.sessionId);
                 // Remove expired sessions as well as child sessions if any
                 mSessions.remove(session.sessionId);
                 // Since this is early during boot we don't send
@@ -841,7 +843,7 @@
                 mSilentUpdatePolicy, mInstallThread.getLooper(), mStagingManager, sessionId,
                 userId, callingUid, installSource, params, createdMillis, 0L, stageDir, stageCid,
                 null, null, false, false, false, false, null, SessionInfo.INVALID_ID,
-                false, false, false, SessionInfo.STAGED_SESSION_NO_ERROR, "");
+                false, false, false, SessionInfo.SESSION_NO_ERROR, "");
 
         synchronized (mSessions) {
             mSessions.put(sessionId, session);
@@ -850,6 +852,9 @@
         mCallbacks.notifySessionCreated(session.sessionId, session.userId);
 
         mSettingsWriteRequest.schedule();
+        if (LOGD) {
+            Slog.d(TAG, "Created session id=" + sessionId + " staged=" + params.isStaged);
+        }
         return sessionId;
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index f45e54b..304ad72 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -460,7 +460,7 @@
     @GuardedBy("mLock")
     private boolean mSessionFailed;
     @GuardedBy("mLock")
-    private int mSessionErrorCode = SessionInfo.STAGED_SESSION_NO_ERROR;
+    private int mSessionErrorCode = SessionInfo.SESSION_NO_ERROR;
     @GuardedBy("mLock")
     private String mSessionErrorMessage;
 
@@ -2092,7 +2092,7 @@
         if (isStaged()) {
             // This will clean up the session when it reaches the terminal state
             mStagedSession.setSessionFailed(
-                    SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, msgWithErrorCode);
+                    SessionInfo.SESSION_VERIFICATION_FAILED, msgWithErrorCode);
             mStagedSession.notifyEndPreRebootVerification();
         } else {
             // Session is sealed and committed but could not be verified, we need to destroy it.
@@ -2547,7 +2547,7 @@
         if (isStaged()) {
             mSessionProvider.getSessionVerifier().verifyStaged(mStagedSession, (error, msg) -> {
                 mStagedSession.notifyEndPreRebootVerification();
-                if (error == SessionInfo.STAGED_SESSION_NO_ERROR) {
+                if (error == SessionInfo.SESSION_NO_ERROR) {
                     mStagingManager.commitSession(mStagedSession);
                 }
             });
@@ -4168,7 +4168,7 @@
             mSessionReady = true;
             mSessionApplied = false;
             mSessionFailed = false;
-            mSessionErrorCode = SessionInfo.STAGED_SESSION_NO_ERROR;
+            mSessionErrorCode = SessionInfo.SESSION_NO_ERROR;
             mSessionErrorMessage = "";
         }
         mCallback.onSessionChanged(this);
@@ -4196,7 +4196,7 @@
             mSessionReady = false;
             mSessionApplied = true;
             mSessionFailed = false;
-            mSessionErrorCode = SessionInfo.STAGED_SESSION_NO_ERROR;
+            mSessionErrorCode = SessionInfo.SESSION_NO_ERROR;
             mSessionErrorMessage = "";
             Slog.d(TAG, "Marking session " + sessionId + " as applied");
         }
@@ -4705,7 +4705,7 @@
         final boolean isFailed = in.getAttributeBoolean(null, ATTR_IS_FAILED, false);
         final boolean isApplied = in.getAttributeBoolean(null, ATTR_IS_APPLIED, false);
         final int sessionErrorCode = in.getAttributeInt(null, ATTR_SESSION_ERROR_CODE,
-                SessionInfo.STAGED_SESSION_NO_ERROR);
+                SessionInfo.SESSION_NO_ERROR);
         final String sessionErrorMessage = readStringAttribute(in, ATTR_SESSION_ERROR_MESSAGE);
 
         if (!isStagedSessionStateValid(isReady, isApplied, isFailed)) {
diff --git a/services/core/java/com/android/server/pm/PackageSessionVerifier.java b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
index a532fe3..ccabce7 100644
--- a/services/core/java/com/android/server/pm/PackageSessionVerifier.java
+++ b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
@@ -202,7 +202,7 @@
     }
 
     private void onVerificationSuccess(StagingManager.StagedSession session, Callback callback) {
-        callback.onResult(SessionInfo.STAGED_SESSION_NO_ERROR, null);
+        callback.onResult(SessionInfo.SESSION_NO_ERROR, null);
     }
 
     private void onVerificationFailure(StagingManager.StagedSession session, Callback callback,
@@ -298,7 +298,7 @@
             // Failed to get hold of StorageManager
             Slog.e(TAG, "Failed to get hold of StorageManager", e);
             throw new PackageManagerException(
-                    SessionInfo.STAGED_SESSION_UNKNOWN,
+                    SessionInfo.SESSION_UNKNOWN_ERROR,
                     "Failed to get hold of StorageManager");
         }
         // Proactively mark session as ready before calling apexd. Although this call order
@@ -336,7 +336,7 @@
         final ParseResult<SigningDetails> newResult = ApkSignatureVerifier.verify(
                 input.reset(), apexPath, minSignatureScheme);
         if (newResult.isError()) {
-            throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+            throw new PackageManagerException(SessionInfo.SESSION_VERIFICATION_FAILED,
                     "Failed to parse APEX package " + apexPath + " : "
                             + newResult.getException(), newResult.getException());
         }
@@ -355,7 +355,7 @@
                 input.reset(), existingApexPkg.applicationInfo.sourceDir,
                 SigningDetails.SignatureSchemeVersion.JAR);
         if (existingResult.isError()) {
-            throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+            throw new PackageManagerException(SessionInfo.SESSION_VERIFICATION_FAILED,
                     "Failed to parse APEX package " + existingApexPkg.applicationInfo.sourceDir
                             + " : " + existingResult.getException(), existingResult.getException());
         }
@@ -369,7 +369,7 @@
             return;
         }
 
-        throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+        throw new PackageManagerException(SessionInfo.SESSION_VERIFICATION_FAILED,
                 "APK-container signature of APEX package " + packageName + " with version "
                         + newApexPkg.versionCodeMajor + " and path " + apexPath + " is not"
                         + " compatible with the one currently installed on device");
@@ -412,11 +412,11 @@
                 packageInfo = PackageInfoWithoutStateUtils.generate(parsedPackage, apexInfo, flags);
                 if (packageInfo == null) {
                     throw new PackageManagerException(
-                            SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                            SessionInfo.SESSION_VERIFICATION_FAILED,
                             "Unable to generate package info: " + apexInfo.modulePath);
                 }
             } catch (PackageManagerException e) {
-                throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                throw new PackageManagerException(SessionInfo.SESSION_VERIFICATION_FAILED,
                         "Failed to parse APEX package " + apexInfo.modulePath + " : " + e, e);
             }
             result.add(packageInfo);
@@ -438,7 +438,7 @@
             }
         }
         throw new PackageManagerException(
-                SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                SessionInfo.SESSION_VERIFICATION_FAILED,
                 "Could not find rollback id for commit session: " + sessionId);
     }
 
@@ -546,7 +546,7 @@
         try {
             checkActiveSessions(PackageHelper.getStorageManager().supportsCheckpoint());
         } catch (RemoteException e) {
-            throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+            throw new PackageManagerException(SessionInfo.SESSION_VERIFICATION_FAILED,
                     "Can't query fs-checkpoint status : " + e);
         }
     }
@@ -562,7 +562,7 @@
         }
         if (!supportsCheckpoint && activeSessions > 1) {
             throw new PackageManagerException(
-                    SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                    SessionInfo.SESSION_VERIFICATION_FAILED,
                     "Cannot stage multiple sessions without checkpoint support");
         }
     }
@@ -593,13 +593,13 @@
                     // will be deleted.
                 }
                 stagedSession.setSessionFailed(
-                        SessionInfo.STAGED_SESSION_CONFLICT,
+                        SessionInfo.SESSION_CONFLICT,
                         "Session was failed by rollback session: " + session.sessionId());
                 Slog.i(TAG, "Session " + stagedSession.sessionId() + " is marked failed due to "
                         + "rollback session: " + session.sessionId());
             } else if (!isRollback(session) && isRollback(stagedSession)) {
                 throw new PackageManagerException(
-                        SessionInfo.STAGED_SESSION_CONFLICT,
+                        SessionInfo.SESSION_CONFLICT,
                         "Session was failed by rollback session: " + stagedSession.sessionId());
 
             }
@@ -622,7 +622,7 @@
         final String packageName = child.getPackageName();
         if (packageName == null) {
             throw new PackageManagerException(
-                    SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                    SessionInfo.SESSION_VERIFICATION_FAILED,
                     "Cannot stage session " + child.sessionId() + " with package name null");
         }
         for (StagingManager.StagedSession stagedSession : mStagedSessions) {
@@ -634,14 +634,14 @@
                 if (stagedSession.getCommittedMillis() < parent.getCommittedMillis()) {
                     // Fail the session committed later when there are overlapping packages
                     throw new PackageManagerException(
-                            SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                            SessionInfo.SESSION_VERIFICATION_FAILED,
                             "Package: " + packageName + " in session: "
                                     + child.sessionId()
                                     + " has been staged already by session: "
                                     + stagedSession.sessionId());
                 } else {
                     stagedSession.setSessionFailed(
-                            SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                            SessionInfo.SESSION_VERIFICATION_FAILED,
                             "Package: " + packageName + " in session: "
                                     + stagedSession.sessionId()
                                     + " has been staged already by session: "
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 8a6ef6b..29de555 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -284,7 +284,7 @@
             String packageName = apexSession.getPackageName();
             String errorMsg = mApexManager.getApkInApexInstallError(packageName);
             if (errorMsg != null) {
-                throw new PackageManagerException(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+                throw new PackageManagerException(SessionInfo.SESSION_ACTIVATION_FAILED,
                         "Failed to install apk-in-apex of " + packageName + " : " + errorMsg);
             }
         }
@@ -397,7 +397,7 @@
                 revertMsg += " Reason for revert: " + reasonForRevert;
             }
             Slog.d(TAG, revertMsg);
-            session.setSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, revertMsg);
+            session.setSessionFailed(SessionInfo.SESSION_UNKNOWN_ERROR, revertMsg);
             return;
         }
 
@@ -484,7 +484,7 @@
             for (String apkInApex : mApexManager.getApksInApex(packageName)) {
                 if (!apkNames.add(apkInApex)) {
                     throw new PackageManagerException(
-                            SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+                            SessionInfo.SESSION_ACTIVATION_FAILED,
                             "Package: " + packageName + " in session: "
                                     + apexSession.sessionId() + " has duplicate apk-in-apex: "
                                     + apkInApex, null);
@@ -511,7 +511,7 @@
             Slog.e(TAG, "Failure to install APK staged session "
                     + session.sessionId() + " [" + errorMessage + "]");
             throw new PackageManagerException(
-                    SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMessage);
+                    SessionInfo.SESSION_ACTIVATION_FAILED, errorMessage);
         }
     }
 
@@ -665,7 +665,7 @@
             // is upgrading. Fail all the sessions and exit early.
             for (int i = 0; i < sessions.size(); i++) {
                 StagedSession session = sessions.get(i);
-                session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+                session.setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED,
                         "Build fingerprint has changed");
             }
             return;
@@ -705,7 +705,7 @@
             final ApexSessionInfo apexSession = apexSessions.get(session.sessionId());
             if (apexSession == null || apexSession.isUnknown) {
                 hasFailedApexSession = true;
-                session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, "apexd did "
+                session.setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED, "apexd did "
                         + "not know anything about a staged session supposed to be activated");
                 continue;
             } else if (isApexSessionFailed(apexSession)) {
@@ -721,7 +721,7 @@
                     errorMsg += " Error: " + apexSession.errorMessage;
                 }
                 Slog.d(TAG, errorMsg);
-                session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg);
+                session.setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED, errorMsg);
                 continue;
             } else if (apexSession.isActivated || apexSession.isSuccess) {
                 hasAppliedApexSession = true;
@@ -730,13 +730,13 @@
                 // Apexd did not apply the session for some unknown reason. There is no guarantee
                 // that apexd will install it next time. Safer to proactively mark it as failed.
                 hasFailedApexSession = true;
-                session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+                session.setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED,
                         "Staged session " + session.sessionId() + " at boot didn't activate nor "
                         + "fail. Marking it as failed anyway.");
             } else {
                 Slog.w(TAG, "Apex session " + session.sessionId() + " is in impossible state");
                 hasFailedApexSession = true;
-                session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+                session.setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED,
                         "Impossible state");
             }
         }
@@ -756,7 +756,7 @@
                     // Session has been already failed in the loop above.
                     continue;
                 }
-                session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+                session.setSessionFailed(SessionInfo.SESSION_ACTIVATION_FAILED,
                         "Another apex session failed");
             }
             return;
@@ -772,7 +772,7 @@
             } catch (Exception e) {
                 Slog.e(TAG, "Staged install failed due to unhandled exception", e);
                 onInstallationFailure(session, new PackageManagerException(
-                        SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+                        SessionInfo.SESSION_ACTIVATION_FAILED,
                         "Staged install failed due to unhandled exception: " + e),
                         supportsCheckpoint, needsCheckpoint);
             }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 8d56cda..1cfcdf51 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -386,7 +386,8 @@
     @Override
     public void startOneTimePermissionSession(String packageName, @UserIdInt int userId,
             long timeoutMillis, int importanceToResetTimer, int importanceToKeepSessionAlive) {
-        mContext.enforceCallingPermission(Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS,
+        mContext.enforceCallingOrSelfPermission(
+                Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS,
                 "Must hold " + Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS
                         + " to register permissions as one time.");
         Objects.requireNonNull(packageName);
@@ -551,6 +552,18 @@
     }
 
     @Override
+    public void revokePostNotificationPermissionWithoutKillForTest(String packageName, int userId) {
+        mPermissionManagerServiceImpl.revokePostNotificationPermissionWithoutKillForTest(
+                packageName, userId);
+    }
+
+    @Override
+    public void selfRevokePermissions(@NonNull String packageName,
+            @NonNull List<String> permissions) {
+        mPermissionManagerServiceImpl.selfRevokePermissions(packageName, permissions);
+    }
+
+    @Override
     public boolean shouldShowRequestPermissionRationale(String packageName, String permissionName,
             int userId) {
         return mPermissionManagerServiceImpl.shouldShowRequestPermissionRationale(packageName,
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index c1bedb2..7833c43 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -167,6 +167,10 @@
     private static final String TAG = "PackageManager";
     private static final String LOG_TAG = PermissionManagerServiceImpl.class.getSimpleName();
 
+    private static final String SKIP_KILL_APP_REASON_NOTIFICATION_TEST = "skip permission revoke "
+            + "app kill for notification test";
+
+
     private static final long BACKUP_TIMEOUT_MILLIS = SECONDS.toMillis(60);
 
     // For automotive products, CarService enforces allow-listing of the privileged permissions
@@ -320,11 +324,15 @@
             mPackageManagerInt.writeSettings(true);
         }
         @Override
-        public void onPermissionRevoked(int uid, int userId, String reason) {
+        public void onPermissionRevoked(int uid, int userId, String reason, boolean overrideKill) {
             mOnPermissionChangeListeners.onPermissionsChanged(uid);
 
             // Critical; after this call the application should never have the permission
             mPackageManagerInt.writeSettings(false);
+            if (overrideKill) {
+                return;
+            }
+
             final int appId = UserHandle.getAppId(uid);
             if (reason == null) {
                 mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED));
@@ -1438,9 +1446,29 @@
                 reason, mDefaultPermissionCallback);
     }
 
+    @Override
+    public void revokePostNotificationPermissionWithoutKillForTest(String packageName, int userId) {
+        final int callingUid = Binder.getCallingUid();
+        final boolean overridePolicy =
+                checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY)
+                        == PackageManager.PERMISSION_GRANTED;
+        mContext.enforceCallingPermission(
+                android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL, "");
+        revokeRuntimePermissionInternal(packageName, Manifest.permission.POST_NOTIFICATIONS,
+                overridePolicy, true, callingUid, userId,
+                SKIP_KILL_APP_REASON_NOTIFICATION_TEST, mDefaultPermissionCallback);
+    }
+
     private void revokeRuntimePermissionInternal(String packageName, String permName,
-            boolean overridePolicy, int callingUid, final int userId, String reason,
-            PermissionCallback callback) {
+            boolean overridePolicy, int callingUid, final int userId,
+            String reason, PermissionCallback callback) {
+        revokeRuntimePermissionInternal(packageName, permName, overridePolicy, false, callingUid,
+                userId, reason, callback);
+    }
+
+    private void revokeRuntimePermissionInternal(String packageName, String permName,
+            boolean overridePolicy, boolean overrideKill, int callingUid, final int userId,
+            String reason, PermissionCallback callback) {
         if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES
                 && PermissionManager.shouldTraceGrant(packageName, permName, userId)) {
             Log.i(TAG, "System is revoking " + packageName + " "
@@ -1552,7 +1580,7 @@
         if (callback != null) {
             if (isRuntimePermission) {
                 callback.onPermissionRevoked(UserHandle.getUid(userId, pkg.getUid()), userId,
-                        reason);
+                        reason, overrideKill);
             } else {
                 mDefaultPermissionCallback.onInstallPermissionRevoked();
             }
@@ -1563,6 +1591,25 @@
         }
     }
 
+    @Override
+    public void selfRevokePermissions(String packageName, List<String> permissions) {
+        final int callingUid = Binder.getCallingUid();
+        int callingUserId = UserHandle.getUserId(callingUid);
+        int targetPackageUid = mPackageManagerInt.getPackageUid(packageName, 0, callingUserId);
+        if (targetPackageUid != callingUid) {
+            throw new SecurityException("uid " + callingUid
+                    + " cannot revoke permissions for package " + packageName + " with uid "
+                    + targetPackageUid);
+        }
+        for (String permName : permissions) {
+            if (!checkCallingOrSelfPermission(permName)) {
+                throw new SecurityException("uid " + callingUid + " cannot revoke permission "
+                        + permName + " because it does not hold that permission");
+            }
+        }
+        mPermissionControllerManager.selfRevokePermissions(packageName, permissions);
+    }
+
     private boolean mayManageRolePermission(int uid) {
         final PackageManager packageManager = mContext.getPackageManager();
         final String[] packageNames = packageManager.getPackagesForUid(uid);
@@ -5197,7 +5244,11 @@
         public void onPermissionChanged() {}
         public void onPermissionGranted(int uid, @UserIdInt int userId) {}
         public void onInstallPermissionGranted() {}
-        public void onPermissionRevoked(int uid, @UserIdInt int userId, String reason) {}
+        public void onPermissionRevoked(int uid, @UserIdInt int userId, String reason) {
+            onPermissionRevoked(uid, userId, reason, false);
+        }
+        public void onPermissionRevoked(int uid, @UserIdInt int userId, String reason,
+                boolean overrideKill) {}
         public void onInstallPermissionRevoked() {}
         public void onPermissionUpdated(@UserIdInt int[] updatedUserIds, boolean sync) {}
         public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, boolean sync,
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
index d2018f2..c582f9e 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
@@ -318,6 +318,35 @@
             String reason);
 
     /**
+     * Revoke the POST_NOTIFICATIONS permission, without killing the app. This method must ONLY BE
+     * USED in CTS or local tests.
+     *
+     * @param packageName The package to be revoked
+     * @param userId The user for which to revoke
+     */
+    void revokePostNotificationPermissionWithoutKillForTest(String packageName, int userId);
+
+    /**
+     * Triggers the revocation of one or more permissions for a package, under the following
+     * conditions:
+     * <ul>
+     * <li>The package {@code packageName} must be under the same UID as the calling process
+     *   (typically, the target package is the calling package).
+     * <li>Each permission in {@code permissions} must be granted to the package
+     * {@code packageName}.
+     * <li>Each permission in {@code permissions} must be a runtime permission.
+     * </ul>
+     * <p>
+     * For every permission in {@code permissions}, the entire permission group it belongs to will
+     * be revoked. This revocation happens asynchronously and kills all processes running in the
+     * same UID as {@code packageName}. It will be triggered once it is safe to do so.
+     *
+     * @param packageName The name of the package for which the permissions will be revoked.
+     * @param permissions List of permissions to be revoked.
+     */
+    void selfRevokePermissions(String packageName, List<String> permissions);
+
+    /**
      * Get whether you should show UI with rationale for requesting a permission. You should do this
      * only if you do not have the permission and the context in which the permission is requested
      * does not clearly communicate to the user what would be the benefit from grating this
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 10d9bbf..c9a8701 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -1139,13 +1139,11 @@
         private boolean shouldForceShowNotificationPermissionRequest(@NonNull String pkgName,
                 @NonNull UserHandle user) {
             AndroidPackage pkg = mPackageManagerInternal.getPackage(pkgName);
-            // TODO(b/205888750): Remove platform key and permissionController lines after pregrants
-            // are in place
-            if (pkg == null || pkg.getPackageName() == null || pkg.isSignedWithPlatformKey()
-                    || pkg.getPackageName().contains("nexuslauncher")
+            if (pkg == null || pkg.getPackageName() == null
                     || Objects.equals(pkgName, mPackageManager.getPermissionControllerPackageName())
                     || pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) {
-                // TODO(b/205888750) add warning logs when pregrants in place
+                Slog.w(LOG_TAG, "Cannot check for Notification prompt, no package for "
+                        + pkgName + " or pkg is Permission Controller");
                 return false;
             }
 
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 1e74104..4b2770c 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -49,6 +49,7 @@
 import android.database.ContentObserver;
 import android.hardware.SensorManager;
 import android.hardware.SystemSensorManager;
+import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
@@ -60,6 +61,7 @@
 import android.os.BatterySaverPolicyConfig;
 import android.os.Binder;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.IPowerManager;
 import android.os.Looper;
@@ -1151,6 +1153,9 @@
                             PowerManager.GO_TO_SLEEP_REASON_QUIESCENT,
                             PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, Process.SYSTEM_UID);
                 }
+
+                mContext.getSystemService(DeviceStateManager.class).registerCallback(
+                        new HandlerExecutor(mHandler), new DeviceStateListener());
             }
         }
     }
@@ -6349,4 +6354,25 @@
             return interceptPowerKeyDownInternal(event);
         }
     }
+
+    /**
+     * Listens to changes in device state and updates the interactivity time.
+     * Any changes to the device state are treated as user interactions.
+     */
+    class DeviceStateListener implements DeviceStateManager.DeviceStateCallback {
+        private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
+
+        @Override
+        public void onStateChanged(int deviceState) {
+            if (mDeviceState != deviceState) {
+                mDeviceState = deviceState;
+                // Device-state interactions are applied to the default display so that they
+                // are reflected only with the default power group.
+                userActivityInternal(Display.DEFAULT_DISPLAY, mClock.uptimeMillis(),
+                        PowerManager.USER_ACTIVITY_EVENT_DEVICE_STATE, /* flags= */0,
+                        Process.SYSTEM_UID);
+            }
+        }
+    };
+
 }
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
index ef0079e..ca67597 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
@@ -35,7 +35,6 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
-
 import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
 import com.android.server.powerstats.ProtoStreamUtils.ChannelUtils;
 import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerResultUtils;
@@ -313,12 +312,12 @@
         return mStartWallTime;
     }
 
-    public PowerStatsLogger(Context context, File dataStoragePath,
+    public PowerStatsLogger(Context context, Looper looper, File dataStoragePath,
             String meterFilename, String meterCacheFilename,
             String modelFilename, String modelCacheFilename,
             String residencyFilename, String residencyCacheFilename,
             IPowerStatsHALWrapper powerStatsHALWrapper) {
-        super(Looper.getMainLooper());
+        super(looper);
         mStartWallTime = currentTimeMillis() - SystemClock.elapsedRealtime();
         if (DEBUG) Slog.d(TAG, "mStartWallTime: " + mStartWallTime);
         mPowerStatsHALWrapper = powerStatsHALWrapper;
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index bb52c1d..9953ca8 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -28,6 +28,7 @@
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.Looper;
 import android.os.UserHandle;
 import android.power.PowerStatsInternal;
 import android.util.Slog;
@@ -79,6 +80,9 @@
     private StatsPullAtomCallbackImpl mPullAtomCallback;
     @Nullable
     private PowerStatsInternal mPowerStatsInternal;
+    @Nullable
+    @GuardedBy("this")
+    private Looper mLooper;
 
     @VisibleForTesting
     static class Injector {
@@ -127,12 +131,12 @@
             }
         }
 
-        PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath,
-                String meterFilename, String meterCacheFilename,
+        PowerStatsLogger createPowerStatsLogger(Context context, Looper looper,
+                File dataStoragePath, String meterFilename, String meterCacheFilename,
                 String modelFilename, String modelCacheFilename,
                 String residencyFilename, String residencyCacheFilename,
                 IPowerStatsHALWrapper powerStatsHALWrapper) {
-            return new PowerStatsLogger(context, dataStoragePath,
+            return new PowerStatsLogger(context, looper, dataStoragePath,
                 meterFilename, meterCacheFilename,
                 modelFilename, modelCacheFilename,
                 residencyFilename, residencyCacheFilename,
@@ -229,11 +233,11 @@
             mDataStoragePath = mInjector.createDataStoragePath();
 
             // Only start logger and triggers if initialization is successful.
-            mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext, mDataStoragePath,
-                mInjector.createMeterFilename(), mInjector.createMeterCacheFilename(),
-                mInjector.createModelFilename(), mInjector.createModelCacheFilename(),
-                mInjector.createResidencyFilename(), mInjector.createResidencyCacheFilename(),
-                getPowerStatsHal());
+            mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext, getLooper(),
+                    mDataStoragePath, mInjector.createMeterFilename(),
+                    mInjector.createMeterCacheFilename(), mInjector.createModelFilename(),
+                    mInjector.createModelCacheFilename(), mInjector.createResidencyFilename(),
+                    mInjector.createResidencyCacheFilename(), getPowerStatsHal());
             mBatteryTrigger = mInjector.createBatteryTrigger(mContext, mPowerStatsLogger);
             mTimerTrigger = mInjector.createTimerTrigger(mContext, mPowerStatsLogger);
         } else {
@@ -245,6 +249,17 @@
         return mInjector.getPowerStatsHALWrapperImpl();
     }
 
+    private Looper getLooper() {
+        synchronized (this) {
+            if (mLooper == null) {
+                HandlerThread thread = new HandlerThread(TAG);
+                thread.start();
+                return thread.getLooper();
+            }
+            return mLooper;
+        }
+    }
+
     public PowerStatsService(Context context) {
         this(context, new Injector());
     }
@@ -260,9 +275,7 @@
         private final Handler mHandler;
 
         LocalService() {
-            HandlerThread thread = new HandlerThread(TAG);
-            thread.start();
-            mHandler = new Handler(thread.getLooper());
+            mHandler = new Handler(getLooper());
         }
 
 
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 7f50cd6..f0bf86f 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -3435,7 +3435,8 @@
                     convertTimeZoneSuggestionToProtoBytes(
                             metricsState.getLatestTelephonySuggestion()),
                     convertTimeZoneSuggestionToProtoBytes(
-                            metricsState.getLatestGeolocationSuggestion())
+                            metricsState.getLatestGeolocationSuggestion()),
+                    metricsState.isTelephonyTimeZoneFallbackSupported()
             ));
         } catch (RuntimeException e) {
             Slog.e(TAG, "Getting time zone detection state failed: ", e);
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index e98fa28..fe74167 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -528,12 +528,13 @@
                     callback.onFailure();
                     return;
                 }
-                textClassifierServiceConsumer.accept(serviceState.mService);
+                consumeServiceNoExceptLocked(textClassifierServiceConsumer, serviceState.mService);
             } else {
                 serviceState.mPendingRequests.add(
                         new PendingRequest(
                                 methodName,
-                                () -> textClassifierServiceConsumer.accept(serviceState.mService),
+                                () -> consumeServiceNoExceptLocked(
+                                        textClassifierServiceConsumer, serviceState.mService),
                                 callback::onFailure, callback.asBinder(),
                                 this,
                                 serviceState,
@@ -542,6 +543,16 @@
         }
     }
 
+    private static void consumeServiceNoExceptLocked(
+            @NonNull ThrowingConsumer<ITextClassifierService> textClassifierServiceConsumer,
+            @Nullable ITextClassifierService service) {
+        try {
+            textClassifierServiceConsumer.accept(service);
+        } catch (RuntimeException | Error e) {
+            Slog.e(LOG_TAG, "Exception when consume textClassifierService: " + e);
+        }
+    }
+
     private static ITextClassifierCallback wrap(ITextClassifierCallback orig) {
         return new CallbackWrapper(orig);
     }
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 568e4b8..e066ca3 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -69,6 +69,7 @@
 import android.media.tv.TvInputService;
 import android.media.tv.TvStreamConfig;
 import android.media.tv.TvTrackInfo;
+import android.media.tv.tunerresourcemanager.TunerResourceManager;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -2538,6 +2539,31 @@
         }
 
         @Override
+        public int getClientPriority(int useCase, String sessionId) {
+            final int callingPid = Binder.getCallingPid();
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                int clientPid = TvInputManager.UNKNOWN_CLIENT_PID;
+                if (sessionId != null) {
+                    synchronized (mLock) {
+                        try {
+                            clientPid = getClientPidLocked(sessionId);
+                        } catch (ClientPidNotFoundException e) {
+                            Slog.e(TAG, "error in getClientPriority", e);
+                        }
+                    }
+                } else {
+                    clientPid = callingPid;
+                }
+                TunerResourceManager trm = (TunerResourceManager)
+                        mContext.getSystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE);
+                return trm.getClientPriority(useCase, clientPid);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public List<TunedInfo> getCurrentTunedInfos(@UserIdInt int userId) {
             if (mContext.checkCallingPermission(android.Manifest.permission.ACCESS_TUNED_INFO)
                     != PackageManager.PERMISSION_GRANTED) {
@@ -2585,9 +2611,9 @@
 
         @GuardedBy("mLock")
         private int getClientPidLocked(String sessionId)
-                throws IllegalStateException {
+                throws ClientPidNotFoundException {
             if (mSessionIdToSessionStateMap.get(sessionId) == null) {
-                throw new IllegalStateException("Client Pid not found with sessionId "
+                throw new ClientPidNotFoundException("Client Pid not found with sessionId "
                         + sessionId);
             }
             return mSessionIdToSessionStateMap.get(sessionId).callingPid;
diff --git a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
index a2bf2fe..a4732c1 100644
--- a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
@@ -906,6 +906,94 @@
         }
 
         @Override
+        public void notifyVideoAvailable(IBinder sessionToken, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+                    "notifyVideoAvailable");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).notifyVideoAvailable();
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyVideoAvailable", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void notifyVideoUnavailable(IBinder sessionToken, int reason, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+                    "notifyVideoUnavailable");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).notifyVideoUnavailable(reason);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyVideoUnavailable", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void notifyContentAllowed(IBinder sessionToken, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+                    "notifyContentAllowed");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).notifyContentAllowed();
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyContentAllowed", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void notifyContentBlocked(IBinder sessionToken, String rating, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+                    "notifyContentBlocked");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).notifyContentBlocked(rating);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyContentBlocked", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void startIApp(IBinder sessionToken, int userId) {
             if (DEBUG) {
                 Slogf.d(TAG, "BinderService#start(userId=%d)", userId);
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 63f4c68..af705d5 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -289,6 +289,23 @@
         }
 
         @Override
+        public boolean transferOwner(int resourceType, int currentOwnerId, int newOwnerId) {
+            enforceTunerAccessPermission("transferOwner");
+            enforceTrmAccessPermission("transferOwner");
+            synchronized (mLock) {
+                if (!checkClientExists(currentOwnerId)) {
+                    Slog.e(TAG, "currentOwnerId:" + currentOwnerId + " does not exit");
+                    return false;
+                }
+                if (!checkClientExists(newOwnerId)) {
+                    Slog.e(TAG, "newOwnerId:" + newOwnerId + " does not exit");
+                    return false;
+                }
+                return transferOwnerInternal(resourceType, currentOwnerId, newOwnerId);
+            }
+        }
+
+        @Override
         public boolean requestDemux(@NonNull TunerDemuxRequest request,
                     @NonNull int[] demuxHandle) throws RemoteException {
             enforceTunerAccessPermission("requestDemux");
@@ -388,7 +405,11 @@
                 if (fe == null) {
                     throw new RemoteException("Releasing frontend does not exist.");
                 }
-                if (fe.getOwnerClientId() != clientId) {
+                int ownerClientId = fe.getOwnerClientId();
+                ClientProfile ownerProfile = getClientProfile(ownerClientId);
+                if (ownerClientId != clientId
+                        && (ownerProfile != null
+                              && !ownerProfile.getShareFeClientIds().contains(clientId))) {
                     throw new RemoteException(
                             "Client is not the current owner of the releasing fe.");
                 }
@@ -619,6 +640,21 @@
             }
         }
 
+        @Override
+        public int getClientPriority(int useCase, int pid) throws RemoteException {
+            enforceTrmAccessPermission("getClientPriority");
+            synchronized (mLock) {
+                return TunerResourceManagerService.this.getClientPriority(
+                        useCase, checkIsForeground(pid));
+            }
+        }
+        @Override
+        public int getConfigPriority(int useCase, boolean isForeground) throws RemoteException {
+            enforceTrmAccessPermission("getConfigPriority");
+            synchronized (mLock) {
+                return TunerResourceManagerService.this.getClientPriority(useCase, isForeground);
+            }
+        }
     }
 
     /**
@@ -976,6 +1012,83 @@
         getClientProfile(targetClientId).shareFrontend(selfClientId);
     }
 
+    private boolean transferFeOwner(int currentOwnerId, int newOwnerId) {
+        ClientProfile currentOwnerProfile = getClientProfile(currentOwnerId);
+        ClientProfile newOwnerProfile = getClientProfile(newOwnerId);
+        // change the owner of all the inUse frontend
+        newOwnerProfile.shareFrontend(currentOwnerId);
+        currentOwnerProfile.stopSharingFrontend(newOwnerId);
+        for (int inUseHandle : newOwnerProfile.getInUseFrontendHandles()) {
+            getFrontendResource(inUseHandle).setOwner(newOwnerId);
+        }
+        // double check there is no other resources tied to the previous owner
+        for (int inUseHandle : currentOwnerProfile.getInUseFrontendHandles()) {
+            int ownerId = getFrontendResource(inUseHandle).getOwnerClientId();
+            if (ownerId != newOwnerId) {
+                Slog.e(TAG, "something is wrong in transferFeOwner:" + inUseHandle
+                        + ", " + ownerId + ", " + newOwnerId);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean transferFeCiCamOwner(int currentOwnerId, int newOwnerId) {
+        ClientProfile currentOwnerProfile = getClientProfile(currentOwnerId);
+        ClientProfile newOwnerProfile = getClientProfile(newOwnerId);
+
+        // link ciCamId to the new profile
+        int ciCamId = currentOwnerProfile.getInUseCiCamId();
+        newOwnerProfile.useCiCam(ciCamId);
+
+        // set the new owner Id
+        CiCamResource ciCam = getCiCamResource(ciCamId);
+        ciCam.setOwner(newOwnerId);
+
+        // unlink cicam resource from the original owner profile
+        currentOwnerProfile.releaseCiCam();
+        return true;
+    }
+
+    private boolean transferLnbOwner(int currentOwnerId, int newOwnerId) {
+        ClientProfile currentOwnerProfile = getClientProfile(currentOwnerId);
+        ClientProfile newOwnerProfile = getClientProfile(newOwnerId);
+
+        Set<Integer> inUseLnbHandles = new HashSet<>();
+        for (Integer lnbHandle : currentOwnerProfile.getInUseLnbHandles()) {
+            // link lnb handle to the new profile
+            newOwnerProfile.useLnb(lnbHandle);
+
+            // set new owner Id
+            LnbResource lnb = getLnbResource(lnbHandle);
+            lnb.setOwner(newOwnerId);
+
+            inUseLnbHandles.add(lnbHandle);
+        }
+
+        // unlink lnb handles from the original owner
+        for (Integer lnbHandle : inUseLnbHandles) {
+            currentOwnerProfile.releaseLnb(lnbHandle);
+        }
+
+        return true;
+    }
+
+    @VisibleForTesting
+    protected boolean transferOwnerInternal(int resourceType, int currentOwnerId, int newOwnerId) {
+        switch (resourceType) {
+            case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND:
+                return transferFeOwner(currentOwnerId, newOwnerId);
+            case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM:
+                return transferFeCiCamOwner(currentOwnerId, newOwnerId);
+            case TunerResourceManager.TUNER_RESOURCE_TYPE_LNB:
+                return transferLnbOwner(currentOwnerId, newOwnerId);
+            default:
+                Slog.e(TAG, "transferOwnerInternal. unsupported resourceType: " + resourceType);
+                return false;
+        }
+    }
+
     @VisibleForTesting
     protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle) {
         if (DEBUG) {
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
index bd8d13b..6db25b7 100644
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
@@ -20,6 +20,8 @@
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
 
@@ -44,7 +46,7 @@
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.VcnContext;
 
-import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Set;
 
 /** @hide */
@@ -76,7 +78,7 @@
     public static int calculatePriorityClass(
             VcnContext vcnContext,
             UnderlyingNetworkRecord networkRecord,
-            LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities,
+            List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
             ParcelUuid subscriptionGroup,
             TelephonySubscriptionSnapshot snapshot,
             UnderlyingNetworkRecord currentlySelected,
@@ -94,7 +96,7 @@
         }
 
         int priorityIndex = 0;
-        for (VcnUnderlyingNetworkTemplate nwPriority : underlyingNetworkPriorities) {
+        for (VcnUnderlyingNetworkTemplate nwPriority : underlyingNetworkTemplates) {
             if (checkMatchesPriorityRule(
                     vcnContext,
                     nwPriority,
@@ -122,7 +124,11 @@
         // TODO: Check Network Quality reported by metric monitors/probers.
 
         final NetworkCapabilities caps = networkRecord.networkCapabilities;
-        if (!networkPriority.allowMetered() && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)) {
+
+        final int meteredMatch = networkPriority.getMetered();
+        final boolean isMetered = !caps.hasCapability(NET_CAPABILITY_NOT_METERED);
+        if (meteredMatch == MATCH_REQUIRED && !isMetered
+                || meteredMatch == MATCH_FORBIDDEN && isMetered) {
             return false;
         }
 
@@ -171,7 +177,8 @@
             return false;
         }
 
-        if (networkPriority.getSsid() != null && networkPriority.getSsid() != caps.getSsid()) {
+        if (!networkPriority.getSsids().isEmpty()
+                && !networkPriority.getSsids().contains(caps.getSsid())) {
             return false;
         }
 
@@ -226,26 +233,31 @@
                         .getSystemService(TelephonyManager.class)
                         .createForSubscriptionId(subId);
 
-        if (!networkPriority.getAllowedOperatorPlmnIds().isEmpty()) {
+        if (!networkPriority.getOperatorPlmnIds().isEmpty()) {
             final String plmnId = subIdSpecificTelephonyMgr.getNetworkOperator();
-            if (!networkPriority.getAllowedOperatorPlmnIds().contains(plmnId)) {
+            if (!networkPriority.getOperatorPlmnIds().contains(plmnId)) {
                 return false;
             }
         }
 
-        if (!networkPriority.getAllowedSpecificCarrierIds().isEmpty()) {
+        if (!networkPriority.getSimSpecificCarrierIds().isEmpty()) {
             final int carrierId = subIdSpecificTelephonyMgr.getSimSpecificCarrierId();
-            if (!networkPriority.getAllowedSpecificCarrierIds().contains(carrierId)) {
+            if (!networkPriority.getSimSpecificCarrierIds().contains(carrierId)) {
                 return false;
             }
         }
 
-        if (!networkPriority.allowRoaming() && !caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)) {
+        final int roamingMatch = networkPriority.getRoaming();
+        final boolean isRoaming = !caps.hasCapability(NET_CAPABILITY_NOT_ROAMING);
+        if (roamingMatch == MATCH_REQUIRED && !isRoaming
+                || roamingMatch == MATCH_FORBIDDEN && isRoaming) {
             return false;
         }
 
-        if (networkPriority.requireOpportunistic()) {
-            if (!isOpportunistic(snapshot, caps.getSubscriptionIds())) {
+        final int opportunisticMatch = networkPriority.getOpportunistic();
+        final boolean isOpportunistic = isOpportunistic(snapshot, caps.getSubscriptionIds());
+        if (opportunisticMatch == MATCH_REQUIRED) {
+            if (!isOpportunistic) {
                 return false;
             }
 
@@ -265,6 +277,8 @@
                             .contains(SubscriptionManager.getActiveDataSubscriptionId())) {
                 return false;
             }
+        } else if (opportunisticMatch == MATCH_FORBIDDEN && !isOpportunistic) {
+            return false;
         }
 
         return true;
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
index df2f0d5..c0488b1 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
@@ -32,7 +32,7 @@
 import com.android.server.vcn.VcnContext;
 
 import java.util.Comparator;
-import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -77,7 +77,7 @@
 
     static Comparator<UnderlyingNetworkRecord> getComparator(
             VcnContext vcnContext,
-            LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities,
+            List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
             ParcelUuid subscriptionGroup,
             TelephonySubscriptionSnapshot snapshot,
             UnderlyingNetworkRecord currentlySelected,
@@ -87,7 +87,7 @@
                     NetworkPriorityClassifier.calculatePriorityClass(
                             vcnContext,
                             left,
-                            underlyingNetworkPriorities,
+                            underlyingNetworkTemplates,
                             subscriptionGroup,
                             snapshot,
                             currentlySelected,
@@ -96,7 +96,7 @@
                     NetworkPriorityClassifier.calculatePriorityClass(
                             vcnContext,
                             right,
-                            underlyingNetworkPriorities,
+                            underlyingNetworkTemplates,
                             subscriptionGroup,
                             snapshot,
                             currentlySelected,
@@ -133,7 +133,7 @@
     void dump(
             VcnContext vcnContext,
             IndentingPrintWriter pw,
-            LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities,
+            List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
             ParcelUuid subscriptionGroup,
             TelephonySubscriptionSnapshot snapshot,
             UnderlyingNetworkRecord currentlySelected,
@@ -145,7 +145,7 @@
                 NetworkPriorityClassifier.calculatePriorityClass(
                         vcnContext,
                         this,
-                        underlyingNetworkPriorities,
+                        underlyingNetworkTemplates,
                         subscriptionGroup,
                         snapshot,
                         currentlySelected,
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 1ee115d..df6ffa2 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -16,13 +16,16 @@
 
 package com.android.server.vibrator;
 
+import static android.os.VibrationAttributes.USAGE_ACCESSIBILITY;
 import static android.os.VibrationAttributes.USAGE_ALARM;
 import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
 import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
+import static android.os.VibrationAttributes.USAGE_MEDIA;
 import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
 import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION;
 import static android.os.VibrationAttributes.USAGE_RINGTONE;
 import static android.os.VibrationAttributes.USAGE_TOUCH;
+import static android.os.VibrationAttributes.USAGE_UNKNOWN;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -44,8 +47,11 @@
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.os.Vibrator.VibrationIntensity;
+import android.os.vibrator.VibrationConfig;
 import android.provider.Settings;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
@@ -109,53 +115,31 @@
     private final List<OnVibratorSettingsChanged> mListeners = new ArrayList<>();
     private final SparseArray<VibrationEffect> mFallbackEffects;
 
-    private final int mRampStepDuration;
-    private final int mRampDownDuration;
+    private final VibrationConfig mVibrationConfig;
 
     @GuardedBy("mLock")
     @Nullable
-    private Vibrator mVibrator;
-    @GuardedBy("mLock")
-    @Nullable
     private AudioManager mAudioManager;
 
     @GuardedBy("mLock")
     private boolean mVibrateInputDevices;
     @GuardedBy("mLock")
-    private boolean mVibrateWhenRinging;
-    @GuardedBy("mLock")
-    private boolean mApplyRampingRinger;
-    @GuardedBy("mLock")
-    private int mHapticFeedbackIntensity;
-    @GuardedBy("mLock")
-    private int mHardwareFeedbackIntensity;
-    @GuardedBy("mLock")
-    private int mNotificationIntensity;
-    @GuardedBy("mLock")
-    private int mRingIntensity;
+    private SparseIntArray mCurrentVibrationIntensities = new SparseIntArray();
     @GuardedBy("mLock")
     private boolean mBatterySaverMode;
 
     VibrationSettings(Context context, Handler handler) {
-        this(context, handler,
-                context.getResources().getInteger(
-                        com.android.internal.R.integer.config_vibrationWaveformRampDownDuration),
-                context.getResources().getInteger(
-                        com.android.internal.R.integer.config_vibrationWaveformRampStepDuration));
+        this(context, handler, new VibrationConfig(context.getResources()));
     }
 
     @VisibleForTesting
-    VibrationSettings(Context context, Handler handler, int rampDownDuration,
-            int rampStepDuration) {
+    VibrationSettings(Context context, Handler handler, VibrationConfig config) {
         mContext = context;
+        mVibrationConfig = config;
         mSettingObserver = new SettingsObserver(handler);
         mUidObserver = new UidObserver();
         mUserReceiver = new UserObserver();
 
-        // TODO(b/191150049): move these to vibrator static config file
-        mRampDownDuration = rampDownDuration;
-        mRampStepDuration = rampStepDuration;
-
         VibrationEffect clickEffect = createEffectFromResource(
                 com.android.internal.R.array.config_virtualKeyVibePattern);
         VibrationEffect doubleClickEffect = createEffectFromResource(
@@ -179,7 +163,6 @@
 
     public void onSystemReady() {
         synchronized (mLock) {
-            mVibrator = mContext.getSystemService(Vibrator.class);
             mAudioManager = mContext.getSystemService(AudioManager.class);
         }
         try {
@@ -214,12 +197,21 @@
         IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
         mContext.registerReceiver(mUserReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
 
+        // Listen to all settings that might affect the result of Vibrator.getVibrationIntensity.
         registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES));
         registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_WHEN_RINGING));
         registerSettingsObserver(Settings.System.getUriFor(Settings.System.APPLY_RAMPING_RINGER));
+        registerSettingsObserver(Settings.System.getUriFor(
+                Settings.System.HAPTIC_FEEDBACK_ENABLED));
+        registerSettingsObserver(
+                Settings.System.getUriFor(Settings.System.ALARM_VIBRATION_INTENSITY));
         registerSettingsObserver(
                 Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_INTENSITY));
         registerSettingsObserver(
+                Settings.System.getUriFor(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY));
+        registerSettingsObserver(
+                Settings.System.getUriFor(Settings.System.MEDIA_VIBRATION_INTENSITY));
+        registerSettingsObserver(
                 Settings.System.getUriFor(Settings.System.NOTIFICATION_VIBRATION_INTENSITY));
         registerSettingsObserver(
                 Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY));
@@ -253,7 +245,7 @@
      * devices without PWLE support.
      */
     public int getRampStepDuration() {
-        return mRampStepDuration;
+        return mVibrationConfig.getRampStepDurationMs();
     }
 
     /**
@@ -261,7 +253,7 @@
      * when a vibration is cancelled or finished at non-zero amplitude.
      */
     public int getRampDownDuration() {
-        return mRampDownDuration;
+        return mVibrationConfig.getRampDownDurationMs();
     }
 
     /**
@@ -270,25 +262,8 @@
      * @param usageHint one of VibrationAttributes.USAGE_*
      * @return The vibration intensity, one of Vibrator.VIBRATION_INTENSITY_*
      */
-    public int getDefaultIntensity(int usageHint) {
-        if (usageHint == USAGE_ALARM) {
-            return Vibrator.VIBRATION_INTENSITY_HIGH;
-        }
-        synchronized (mLock) {
-            if (mVibrator != null) {
-                switch (usageHint) {
-                    case USAGE_RINGTONE:
-                        return mVibrator.getDefaultRingVibrationIntensity();
-                    case USAGE_NOTIFICATION:
-                        return mVibrator.getDefaultNotificationVibrationIntensity();
-                    case USAGE_TOUCH:
-                    case USAGE_HARDWARE_FEEDBACK:
-                    case USAGE_PHYSICAL_EMULATION:
-                        return mVibrator.getDefaultHapticFeedbackIntensity();
-                }
-            }
-        }
-        return Vibrator.VIBRATION_INTENSITY_MEDIUM;
+    public int getDefaultIntensity(@VibrationAttributes.Usage int usageHint) {
+        return mVibrationConfig.getDefaultVibrationIntensity(usageHint);
     }
 
     /**
@@ -297,23 +272,10 @@
      * @param usageHint one of VibrationAttributes.USAGE_*
      * @return The vibration intensity, one of Vibrator.VIBRATION_INTENSITY_*
      */
-    public int getCurrentIntensity(int usageHint) {
+    public int getCurrentIntensity(@VibrationAttributes.Usage int usageHint) {
+        int defaultIntensity = getDefaultIntensity(usageHint);
         synchronized (mLock) {
-            switch (usageHint) {
-                case USAGE_RINGTONE:
-                    return mRingIntensity;
-                case USAGE_NOTIFICATION:
-                    return mNotificationIntensity;
-                case USAGE_TOUCH:
-                    return mHapticFeedbackIntensity;
-                case USAGE_HARDWARE_FEEDBACK:
-                case USAGE_PHYSICAL_EMULATION:
-                    return mHardwareFeedbackIntensity;
-                case USAGE_ALARM:
-                    return Vibrator.VIBRATION_INTENSITY_HIGH;
-                default:
-                    return Vibrator.VIBRATION_INTENSITY_MEDIUM;
-            }
+            return mCurrentVibrationIntensities.get(usageHint, defaultIntensity);
         }
     }
 
@@ -371,7 +333,7 @@
      * for touch and ringtone usages only. All other usages are allowed by this method.
      */
     @GuardedBy("mLock")
-    private boolean shouldVibrateForRingerModeLocked(int usageHint) {
+    private boolean shouldVibrateForRingerModeLocked(@VibrationAttributes.Usage int usageHint) {
         // If audio manager was not loaded yet then assume most restrictive mode.
         int ringerMode = (mAudioManager == null)
                 ? AudioManager.RINGER_MODE_SILENT
@@ -379,18 +341,9 @@
 
         switch (usageHint) {
             case USAGE_TOUCH:
-                // Touch feedback disabled when phone is on silent mode.
-                return ringerMode != AudioManager.RINGER_MODE_SILENT;
             case USAGE_RINGTONE:
-                switch (ringerMode) {
-                    case AudioManager.RINGER_MODE_SILENT:
-                        return false;
-                    case AudioManager.RINGER_MODE_VIBRATE:
-                        return true;
-                    default:
-                        // Ringtone vibrations also depend on 2 other settings:
-                        return mVibrateWhenRinging || mApplyRampingRinger;
-                }
+                // Touch feedback and ringtone disabled when phone is on silent mode.
+                return ringerMode != AudioManager.RINGER_MODE_SILENT;
             default:
                 // All other usages ignore ringer mode settings.
                 return true;
@@ -401,64 +354,89 @@
     @VisibleForTesting
     void updateSettings() {
         synchronized (mLock) {
-            mVibrateWhenRinging = getSystemSetting(Settings.System.VIBRATE_WHEN_RINGING, 0) != 0;
-            mApplyRampingRinger = getSystemSetting(Settings.System.APPLY_RAMPING_RINGER, 0) != 0;
-            mHapticFeedbackIntensity = getSystemSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                    getDefaultIntensity(USAGE_TOUCH));
-            mHardwareFeedbackIntensity = getSystemSetting(
-                    Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY,
-                    getHardwareFeedbackIntensityWhenSettingIsMissing(mHapticFeedbackIntensity));
-            mNotificationIntensity = getSystemSetting(
-                    Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                    getDefaultIntensity(USAGE_NOTIFICATION));
-            mRingIntensity = getSystemSetting(Settings.System.RING_VIBRATION_INTENSITY,
+            mVibrateInputDevices = loadSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0;
+
+            int alarmIntensity = toIntensity(
+                    loadSystemSetting(Settings.System.ALARM_VIBRATION_INTENSITY, -1),
+                    getDefaultIntensity(USAGE_ALARM));
+            int defaultHapticFeedbackIntensity = getDefaultIntensity(USAGE_TOUCH);
+            int hapticFeedbackIntensity = toIntensity(
+                    loadSystemSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, -1),
+                    defaultHapticFeedbackIntensity);
+            int positiveHapticFeedbackIntensity = toPositiveIntensity(
+                    hapticFeedbackIntensity, defaultHapticFeedbackIntensity);
+            int hardwareFeedbackIntensity = toIntensity(
+                    loadSystemSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, -1),
+                    positiveHapticFeedbackIntensity);
+            int mediaIntensity = toIntensity(
+                    loadSystemSetting(Settings.System.MEDIA_VIBRATION_INTENSITY, -1),
+                    getDefaultIntensity(USAGE_MEDIA));
+            int defaultNotificationIntensity = getDefaultIntensity(USAGE_NOTIFICATION);
+            int notificationIntensity = toIntensity(
+                    loadSystemSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, -1),
+                    defaultNotificationIntensity);
+            int positiveNotificationIntensity = toPositiveIntensity(
+                    notificationIntensity, defaultNotificationIntensity);
+            int ringIntensity = toIntensity(
+                    loadSystemSetting(Settings.System.RING_VIBRATION_INTENSITY, -1),
                     getDefaultIntensity(USAGE_RINGTONE));
-            mVibrateInputDevices = getSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0;
+
+
+            mCurrentVibrationIntensities.clear();
+            mCurrentVibrationIntensities.put(USAGE_ALARM, alarmIntensity);
+            mCurrentVibrationIntensities.put(USAGE_NOTIFICATION, notificationIntensity);
+            mCurrentVibrationIntensities.put(USAGE_MEDIA, mediaIntensity);
+            mCurrentVibrationIntensities.put(USAGE_UNKNOWN, mediaIntensity);
+
+            // Communication request is not disabled by the notification setting.
+            mCurrentVibrationIntensities.put(USAGE_COMMUNICATION_REQUEST,
+                    positiveNotificationIntensity);
+
+            if (!loadBooleanSetting(Settings.System.VIBRATE_WHEN_RINGING)
+                    && !loadBooleanSetting(Settings.System.APPLY_RAMPING_RINGER)) {
+                // Make sure deprecated boolean setting still disables ringtone vibrations.
+                mCurrentVibrationIntensities.put(USAGE_RINGTONE, Vibrator.VIBRATION_INTENSITY_OFF);
+            } else {
+                mCurrentVibrationIntensities.put(USAGE_RINGTONE, ringIntensity);
+            }
+
+            // This should adapt the behavior preceding the introduction of this new setting
+            // key, which is to apply HAPTIC_FEEDBACK_INTENSITY, unless it's disabled.
+            mCurrentVibrationIntensities.put(USAGE_HARDWARE_FEEDBACK, hardwareFeedbackIntensity);
+            mCurrentVibrationIntensities.put(USAGE_PHYSICAL_EMULATION, hardwareFeedbackIntensity);
+
+            if (!loadBooleanSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED)) {
+                // Make sure deprecated boolean setting still disables touch vibrations.
+                mCurrentVibrationIntensities.put(USAGE_TOUCH, Vibrator.VIBRATION_INTENSITY_OFF);
+            } else {
+                mCurrentVibrationIntensities.put(USAGE_TOUCH, hapticFeedbackIntensity);
+            }
+
+            // A11y is not disabled by any haptic feedback setting.
+            mCurrentVibrationIntensities.put(USAGE_ACCESSIBILITY, positiveHapticFeedbackIntensity);
         }
         notifyListeners();
     }
 
-    /**
-     * Return the value to be used for {@link Settings.System#HARDWARE_HAPTIC_FEEDBACK_INTENSITY}
-     * when the value was not set by the user.
-     *
-     * <p>This should adapt the behavior preceding the introduction of this new setting key, which
-     * is to apply {@link Settings.System#HAPTIC_FEEDBACK_INTENSITY} unless it's disabled.
-     */
-    private int getHardwareFeedbackIntensityWhenSettingIsMissing(int hapticFeedbackIntensity) {
-        if (hapticFeedbackIntensity == Vibrator.VIBRATION_INTENSITY_OFF) {
-            return getDefaultIntensity(USAGE_HARDWARE_FEEDBACK);
-        }
-        return hapticFeedbackIntensity;
-    }
-
     @Override
     public String toString() {
         synchronized (mLock) {
+            StringBuilder vibrationIntensitiesString = new StringBuilder("{");
+            for (int i = 0; i < mCurrentVibrationIntensities.size(); i++) {
+                int usage = mCurrentVibrationIntensities.keyAt(i);
+                int intensity = mCurrentVibrationIntensities.valueAt(i);
+                vibrationIntensitiesString.append(VibrationAttributes.usageToString(usage))
+                        .append("=(").append(intensityToString(intensity))
+                        .append(",default:").append(intensityToString(getDefaultIntensity(usage)))
+                        .append("), ");
+            }
+            vibrationIntensitiesString.append('}');
             return "VibrationSettings{"
-                    + "mVibrateInputDevices=" + mVibrateInputDevices
-                    + ", mVibrateWhenRinging=" + mVibrateWhenRinging
-                    + ", mApplyRampingRinger=" + mApplyRampingRinger
+                    + "mVibratorConfig=" + mVibrationConfig
+                    + ", mVibrateInputDevices=" + mVibrateInputDevices
                     + ", mBatterySaverMode=" + mBatterySaverMode
                     + ", mProcStatesCache=" + mUidObserver.mProcStatesCache
-                    + ", mHapticChannelMaxVibrationAmplitude="
-                    + getHapticChannelMaxVibrationAmplitude()
-                    + ", mRampStepDuration=" + mRampStepDuration
-                    + ", mRampDownDuration=" + mRampDownDuration
-                    + ", mHardwareHapticFeedbackIntensity="
-                    + intensityToString(getCurrentIntensity(USAGE_HARDWARE_FEEDBACK))
-                    + ", mHapticFeedbackIntensity="
-                    + intensityToString(getCurrentIntensity(USAGE_TOUCH))
-                    + ", mHapticFeedbackDefaultIntensity="
-                    + intensityToString(getDefaultIntensity(USAGE_TOUCH))
-                    + ", mNotificationIntensity="
-                    + intensityToString(getCurrentIntensity(USAGE_NOTIFICATION))
-                    + ", mNotificationDefaultIntensity="
-                    + intensityToString(getDefaultIntensity(USAGE_NOTIFICATION))
-                    + ", mRingIntensity="
-                    + intensityToString(getCurrentIntensity(USAGE_RINGTONE))
-                    + ", mRingDefaultIntensity="
-                    + intensityToString(getDefaultIntensity(USAGE_RINGTONE))
+                    + ", mVibrationIntensities=" + vibrationIntensitiesString
                     + '}';
         }
     }
@@ -466,16 +444,28 @@
     /** Write current settings into given {@link ProtoOutputStream}. */
     public void dumpProto(ProtoOutputStream proto) {
         synchronized (mLock) {
+            proto.write(VibratorManagerServiceDumpProto.ALARM_INTENSITY,
+                    getCurrentIntensity(USAGE_ALARM));
+            proto.write(VibratorManagerServiceDumpProto.ALARM_DEFAULT_INTENSITY,
+                    getDefaultIntensity(USAGE_ALARM));
+            proto.write(VibratorManagerServiceDumpProto.HARDWARE_FEEDBACK_INTENSITY,
+                    getCurrentIntensity(USAGE_HARDWARE_FEEDBACK));
+            proto.write(VibratorManagerServiceDumpProto.HARDWARE_FEEDBACK_DEFAULT_INTENSITY,
+                    getDefaultIntensity(USAGE_HARDWARE_FEEDBACK));
             proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
-                    mHapticFeedbackIntensity);
+                    getCurrentIntensity(USAGE_TOUCH));
             proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
                     getDefaultIntensity(USAGE_TOUCH));
+            proto.write(VibratorManagerServiceDumpProto.MEDIA_INTENSITY,
+                    getCurrentIntensity(USAGE_MEDIA));
+            proto.write(VibratorManagerServiceDumpProto.MEDIA_DEFAULT_INTENSITY,
+                    getDefaultIntensity(USAGE_MEDIA));
             proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_INTENSITY,
-                    mNotificationIntensity);
+                    getCurrentIntensity(USAGE_NOTIFICATION));
             proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
                     getDefaultIntensity(USAGE_NOTIFICATION));
             proto.write(VibratorManagerServiceDumpProto.RING_INTENSITY,
-                    mRingIntensity);
+                    getCurrentIntensity(USAGE_RINGTONE));
             proto.write(VibratorManagerServiceDumpProto.RING_DEFAULT_INTENSITY,
                     getDefaultIntensity(USAGE_RINGTONE));
         }
@@ -506,13 +496,29 @@
         }
     }
 
-    private float getHapticChannelMaxVibrationAmplitude() {
-        synchronized (mLock) {
-            return mVibrator == null ? Float.NaN : mVibrator.getHapticChannelMaximumAmplitude();
+    @VibrationIntensity
+    private int toPositiveIntensity(int value, @VibrationIntensity int defaultValue) {
+        if (value == Vibrator.VIBRATION_INTENSITY_OFF) {
+            return defaultValue;
         }
+        return toIntensity(value, defaultValue);
     }
 
-    private int getSystemSetting(String settingName, int defaultValue) {
+    @VibrationIntensity
+    private int toIntensity(int value, @VibrationIntensity int defaultValue) {
+        if ((value < Vibrator.VIBRATION_INTENSITY_OFF)
+                || (value > Vibrator.VIBRATION_INTENSITY_HIGH)) {
+            return defaultValue;
+        }
+        return value;
+    }
+
+    private boolean loadBooleanSetting(String settingKey) {
+        return Settings.System.getIntForUser(mContext.getContentResolver(),
+                settingKey, 0, UserHandle.USER_CURRENT) != 0;
+    }
+
+    private int loadSystemSetting(String settingName, int defaultValue) {
         return Settings.System.getIntForUser(mContext.getContentResolver(),
                 settingName, defaultValue, UserHandle.USER_CURRENT);
     }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0b0b704..c929cbb 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2134,7 +2134,7 @@
                         return activityInfo != null ? activityInfo.applicationInfo : null;
                     });
 
-        final int typeParameter = mWmService.mStartingSurfaceController
+        final int typeParameter = StartingSurfaceController
                 .makeStartingWindowTypeParameter(newTask, taskSwitch, processRunning,
                         allowTaskSnapshot, activityCreated, useEmpty, useLegacy, activityAllDrawn);
 
@@ -6616,13 +6616,20 @@
         return splashScreenThemeResId;
     }
 
+    void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
+            boolean startActivity, ActivityRecord sourceRecord) {
+        showStartingWindow(prev, newTask, taskSwitch, isProcessRunning(), startActivity,
+                sourceRecord);
+    }
+
     /**
      * @param prev Previous activity which contains a starting window.
+     * @param processRunning Whether the client process is running.
      * @param startActivity Whether this activity is just created from starter.
      * @param sourceRecord The source activity which start this activity.
      */
     void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
-            boolean startActivity, ActivityRecord sourceRecord) {
+            boolean processRunning, boolean startActivity, ActivityRecord sourceRecord) {
         if (mTaskOverlay) {
             // We don't show starting window for overlay activities.
             return;
@@ -6647,7 +6654,7 @@
                 && task.getActivity((r) -> !r.finishing && r != this) == null;
 
         final boolean scheduled = addStartingWindow(packageName, resolvedTheme,
-                prev, newTask || newSingleActivity, taskSwitch, isProcessRunning(),
+                prev, newTask || newSingleActivity, taskSwitch, processRunning,
                 allowTaskSnapshot(), activityCreated, mSplashScreenStyleEmpty, allDrawn);
         if (DEBUG_STARTING_WINDOW_VERBOSE && scheduled) {
             Slog.d(TAG, "Scheduled starting window for " + this);
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index ecc8587..87fb290 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -455,6 +455,10 @@
             // Lock the loop to ensure the activities launched in a sequence.
             synchronized (mService.mGlobalLock) {
                 mService.deferWindowLayout();
+                // To avoid creating multiple starting window when creating starting multiples
+                // activities, we defer the creation of the starting window once all start request
+                // are processed
+                mService.mWindowManager.mStartingSurfaceController.beginDeferAddStartingWindow();
                 try {
                     for (int i = 0; i < starters.length; i++) {
                         final int startResult = starters[i].setResultTo(resultTo)
@@ -480,6 +484,7 @@
                         }
                     }
                 } finally {
+                    mService.mWindowManager.mStartingSurfaceController.endDeferAddStartingWindow();
                     mService.continueWindowLayout();
                 }
             }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 1bafd49..ddd624d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -437,9 +437,9 @@
     private static final long START_AS_CALLER_TOKEN_EXPIRED_TIMEOUT =
             START_AS_CALLER_TOKEN_TIMEOUT_IMPL + 20 * MINUTE_IN_MILLIS;
 
-    // Activity tokens of system activities that are delegating their call to
-    // #startActivityByCaller, keyed by the permissionToken granted to the delegate.
-    final HashMap<IBinder, IBinder> mStartActivitySources = new HashMap<>();
+    // The component name of the delegated activities that are allowed to call
+    // #startActivityAsCaller with the one-time used permission token.
+    final HashMap<IBinder, ComponentName> mStartActivitySources = new HashMap<>();
 
     // Permission tokens that have expired, but we remember for error reporting.
     final ArrayList<IBinder> mExpiredStartAsCallerTokens = new ArrayList<>();
@@ -1513,7 +1513,7 @@
     }
 
     @Override
-    public IBinder requestStartActivityPermissionToken(IBinder delegatorToken) {
+    public IBinder requestStartActivityPermissionToken(ComponentName componentName) {
         int callingUid = Binder.getCallingUid();
         if (UserHandle.getAppId(callingUid) != SYSTEM_UID) {
             throw new SecurityException("Only the system process can request a permission token, "
@@ -1521,7 +1521,7 @@
         }
         IBinder permissionToken = new Binder();
         synchronized (mGlobalLock) {
-            mStartActivitySources.put(permissionToken, delegatorToken);
+            mStartActivitySources.put(permissionToken, componentName);
         }
 
         Message expireMsg = PooledLambda.obtainMessage(
@@ -1546,7 +1546,7 @@
         // 1)  The caller is an activity that is part of the core framework, and then only when it
         //     is running as the system.
         // 2)  The caller provides a valid permissionToken.  Permission tokens are one-time use and
-        //     can only be requested by a system activity, which may then delegate this call to
+        //     can only be requested from system uid, which may then delegate this call to
         //     another app.
         final ActivityRecord sourceRecord;
         final int targetUid;
@@ -1557,18 +1557,26 @@
             if (resultTo == null) {
                 throw new SecurityException("Must be called from an activity");
             }
-            final IBinder sourceToken;
+
+            sourceRecord = ActivityRecord.isInAnyTask(resultTo);
+            if (sourceRecord == null) {
+                throw new SecurityException("Called with bad activity token: " + resultTo);
+            }
+            if (sourceRecord.app == null) {
+                throw new SecurityException("Called without a process attached to activity");
+            }
+
+            final ComponentName componentName;
             if (permissionToken != null) {
                 // To even attempt to use a permissionToken, an app must also have this signature
                 // permission.
                 mAmInternal.enforceCallingPermission(
                         android.Manifest.permission.START_ACTIVITY_AS_CALLER,
                         "startActivityAsCaller");
-                // If called with a permissionToken, we want the sourceRecord from the delegator
-                // activity that requested this token.
-                sourceToken = mStartActivitySources.remove(permissionToken);
-                if (sourceToken == null) {
-                    // Invalid permissionToken, check if it recently expired.
+                // If called with a permissionToken, the caller must be the same component that
+                // was allowed to use the permissionToken.
+                componentName = mStartActivitySources.remove(permissionToken);
+                if (!sourceRecord.mActivityComponent.equals(componentName)) {
                     if (mExpiredStartAsCallerTokens.contains(permissionToken)) {
                         throw new SecurityException("Called with expired permission token: "
                                 + permissionToken);
@@ -1578,33 +1586,21 @@
                     }
                 }
             } else {
-                // This method was called directly by the source.
-                sourceToken = resultTo;
-            }
-
-            sourceRecord = ActivityRecord.isInAnyTask(sourceToken);
-            if (sourceRecord == null) {
-                throw new SecurityException("Called with bad activity token: " + sourceToken);
-            }
-            if (sourceRecord.app == null) {
-                throw new SecurityException("Called without a process attached to activity");
-            }
-
-            // Whether called directly or from a delegate, the source activity must be from the
-            // android package.
-            if (!sourceRecord.info.packageName.equals("android")) {
-                throw new SecurityException("Must be called from an activity that is "
-                        + "declared in the android package");
-            }
-
-            if (UserHandle.getAppId(sourceRecord.app.mUid) != SYSTEM_UID) {
-                // This is still okay, as long as this activity is running under the
-                // uid of the original calling activity.
-                if (sourceRecord.app.mUid != sourceRecord.launchedFromUid) {
-                    throw new SecurityException(
-                            "Calling activity in uid " + sourceRecord.app.mUid
-                                    + " must be system uid or original calling uid "
-                                    + sourceRecord.launchedFromUid);
+                // Whether called directly or from a delegate, the source activity must be from the
+                // android package.
+                if (!sourceRecord.info.packageName.equals("android")) {
+                    throw new SecurityException("Must be called from an activity that is "
+                            + "declared in the android package");
+                }
+                if (UserHandle.getAppId(sourceRecord.app.mUid) != SYSTEM_UID) {
+                    // This is still okay, as long as this activity is running under the
+                    // uid of the original calling activity.
+                    if (sourceRecord.app.mUid != sourceRecord.launchedFromUid) {
+                        throw new SecurityException(
+                                "Calling activity in uid " + sourceRecord.app.mUid
+                                        + " must be system uid or original calling uid "
+                                        + sourceRecord.launchedFromUid);
+                    }
                 }
             }
             if (ignoreTargetSecurity) {
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index 47622bc..5919806 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -24,18 +24,17 @@
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
-import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
 import static android.window.DisplayAreaOrganizer.FEATURE_FULLSCREEN_MAGNIFICATION;
 import static android.window.DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT;
 import static android.window.DisplayAreaOrganizer.FEATURE_IME_PLACEHOLDER;
 import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED;
-import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL;
 import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;
 
 import static com.android.server.wm.DisplayAreaPolicyBuilder.Feature;
 import static com.android.server.wm.DisplayAreaPolicyBuilder.HierarchyBuilder;
 
+import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.os.Bundle;
 import android.text.TextUtils;
@@ -88,6 +87,9 @@
      */
     public abstract TaskDisplayArea getDefaultTaskDisplayArea();
 
+    /** Returns the {@link TaskDisplayArea} specified by launch options. */
+    public abstract TaskDisplayArea getTaskDisplayArea(@Nullable Bundle options);
+
     /** Provider for platform-default display area policy. */
     static final class DefaultProvider implements DisplayAreaPolicy.Provider {
         @Override
@@ -134,11 +136,6 @@
                         .except(TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL, TYPE_STATUS_BAR,
                                 TYPE_NOTIFICATION_SHADE)
                         .build())
-                        .addFeature(new Feature.Builder(wmService.mPolicy,
-                                "OneHandedBackgroundPanel",
-                                FEATURE_ONE_HANDED_BACKGROUND_PANEL)
-                                .upTo(TYPE_WALLPAPER)
-                                .build())
                         .addFeature(new Feature.Builder(wmService.mPolicy, "OneHanded",
                                 FEATURE_ONE_HANDED)
                                 .all()
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
index 3d7ac6c..8e21d96 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
@@ -27,12 +27,19 @@
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_LAST;
 import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
+
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityOptions;
 import android.os.Bundle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.window.DisplayAreaOrganizer;
+import android.window.WindowContainerToken;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.policy.WindowManagerPolicy;
 
 import java.util.ArrayList;
@@ -43,6 +50,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.function.BiFunction;
+import java.util.function.Function;
 
 /**
  * A builder for instantiating a complex {@link DisplayAreaPolicy}
@@ -149,6 +157,8 @@
      **/
     @Nullable private BiFunction<Integer, Bundle, RootDisplayArea> mSelectRootForWindowFunc;
 
+    @Nullable private Function<Bundle, TaskDisplayArea> mSelectTaskDisplayAreaFunc;
+
     /** Defines the root hierarchy for the whole logical display. */
     DisplayAreaPolicyBuilder setRootHierarchy(HierarchyBuilder rootHierarchyBuilder) {
         mRootHierarchyBuilder = rootHierarchyBuilder;
@@ -176,14 +186,25 @@
     }
 
     /**
+     * The policy will use this function to find the {@link TaskDisplayArea}.
+     * @see DefaultSelectTaskDisplayAreaFunction as an example.
+     */
+    DisplayAreaPolicyBuilder setSelectTaskDisplayAreaFunc(
+            Function<Bundle, TaskDisplayArea> selectTaskDisplayAreaFunc) {
+        mSelectTaskDisplayAreaFunc = selectTaskDisplayAreaFunc;
+        return this;
+    }
+
+    /**
      * Makes sure the setting meets the requirement:
      * 1. {@link #mRootHierarchyBuilder} must be set.
      * 2. {@link RootDisplayArea} and {@link TaskDisplayArea} must have unique ids.
      * 3. {@link Feature} below the same {@link RootDisplayArea} must have unique ids.
      * 4. There must be exactly one {@link HierarchyBuilder} that contains the IME container.
      * 5. There must be exactly one {@link HierarchyBuilder} that contains the default
-     *    {@link TaskDisplayArea} with id {@link FEATURE_DEFAULT_TASK_CONTAINER}.
-     * 6. None of the ids is greater than {@link FEATURE_VENDOR_LAST}.
+     *    {@link TaskDisplayArea} with id
+     *    {@link DisplayAreaOrganizer#FEATURE_DEFAULT_TASK_CONTAINER}.
+     * 6. None of the ids is greater than {@link DisplayAreaOrganizer#FEATURE_VENDOR_LAST}.
      */
     private void validate() {
         if (mRootHierarchyBuilder == null) {
@@ -250,7 +271,7 @@
      * {@link Feature} below the same {@link RootDisplayArea} must have unique ids, but
      * {@link Feature} below different {@link RootDisplayArea} can have the same id so that we can
      * organize them together.
-     * None of the ids is greater than {@link FEATURE_VENDOR_LAST}
+     * None of the ids is greater than {@link DisplayAreaOrganizer#FEATURE_VENDOR_LAST}
      *
      * @param uniqueIdSet ids of {@link RootDisplayArea} and {@link TaskDisplayArea} that must be
      *                    unique,
@@ -323,7 +344,7 @@
                     mRootHierarchyBuilder.mRoot, displayAreaGroupRoots);
         }
         return new Result(wmService, mRootHierarchyBuilder.mRoot, displayAreaGroupRoots,
-                mSelectRootForWindowFunc);
+                mSelectRootForWindowFunc, mSelectTaskDisplayAreaFunc);
     }
 
     /**
@@ -368,6 +389,51 @@
     }
 
     /**
+     * The default function to find {@link TaskDisplayArea} if there's no other function set
+     * through {@link #setSelectTaskDisplayAreaFunc(Function)}.
+     * <p>
+     * This function returns {@link TaskDisplayArea} specified by
+     * {@link ActivityOptions#getLaunchTaskDisplayArea()} if it is not {@code null}. Otherwise,
+     * returns {@link DisplayContent#getDefaultTaskDisplayArea()}.
+     * </p>
+     */
+    private static class DefaultSelectTaskDisplayAreaFunction implements
+            Function<Bundle, TaskDisplayArea> {
+        private final TaskDisplayArea mDefaultTaskDisplayArea;
+        private final int mDisplayId;
+
+        DefaultSelectTaskDisplayAreaFunction(TaskDisplayArea defaultTaskDisplayArea) {
+            mDefaultTaskDisplayArea = defaultTaskDisplayArea;
+            mDisplayId = defaultTaskDisplayArea.getDisplayId();
+        }
+
+        @Override
+        public TaskDisplayArea apply(@Nullable Bundle options) {
+            if (options == null) {
+                return mDefaultTaskDisplayArea;
+            }
+            final ActivityOptions activityOptions = new ActivityOptions(options);
+            final WindowContainerToken tdaToken = activityOptions.getLaunchTaskDisplayArea();
+            if (tdaToken == null) {
+                return mDefaultTaskDisplayArea;
+            }
+            final TaskDisplayArea tda = WindowContainer.fromBinder(tdaToken.asBinder())
+                    .asTaskDisplayArea();
+            if (tda == null) {
+                ProtoLog.w(WM_DEBUG_WINDOW_ORGANIZER, "The TaskDisplayArea with %s does not "
+                        + "exist.", tdaToken);
+                return mDefaultTaskDisplayArea;
+            }
+            if (tda.getDisplayId() != mDisplayId) {
+                throw new IllegalArgumentException("The specified TaskDisplayArea must attach "
+                        + "to Display#" + mDisplayId + ", but it is in Display#"
+                        + tda.getDisplayId());
+            }
+            return tda;
+        }
+    }
+
+    /**
      *  Builder to define {@link Feature} and {@link DisplayArea} hierarchy under a
      * {@link RootDisplayArea}
      */
@@ -722,11 +788,13 @@
     static class Result extends DisplayAreaPolicy {
         final List<RootDisplayArea> mDisplayAreaGroupRoots;
         final BiFunction<Integer, Bundle, RootDisplayArea> mSelectRootForWindowFunc;
+        private final Function<Bundle, TaskDisplayArea> mSelectTaskDisplayAreaFunc;
         private final TaskDisplayArea mDefaultTaskDisplayArea;
 
         Result(WindowManagerService wmService, RootDisplayArea root,
                 List<RootDisplayArea> displayAreaGroupRoots,
-                BiFunction<Integer, Bundle, RootDisplayArea> selectRootForWindowFunc) {
+                BiFunction<Integer, Bundle, RootDisplayArea> selectRootForWindowFunc,
+                Function<Bundle, TaskDisplayArea> selectTaskDisplayAreaFunc) {
             super(wmService, root);
             mDisplayAreaGroupRoots = Collections.unmodifiableList(displayAreaGroupRoots);
             mSelectRootForWindowFunc = selectRootForWindowFunc;
@@ -740,6 +808,9 @@
                 throw new IllegalStateException(
                         "No display area with FEATURE_DEFAULT_TASK_CONTAINER");
             }
+            mSelectTaskDisplayAreaFunc = selectTaskDisplayAreaFunc != null
+                    ? selectTaskDisplayAreaFunc
+                    : new DefaultSelectTaskDisplayAreaFunction(mDefaultTaskDisplayArea);
         }
 
         @Override
@@ -796,6 +867,12 @@
         public TaskDisplayArea getDefaultTaskDisplayArea() {
             return mDefaultTaskDisplayArea;
         }
+
+        @NonNull
+        @Override
+        public TaskDisplayArea getTaskDisplayArea(@Nullable Bundle options) {
+            return mSelectTaskDisplayAreaFunc.apply(options);
+        }
     }
 
     static class PendingArea {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 55f463d..217fc09 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -218,7 +218,6 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 import android.view.SurfaceSession;
-import android.view.View;
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowManager.DisplayImePolicy;
@@ -1425,7 +1424,7 @@
             mWaitingForConfig = true;
             if (mTransitionController.isShellTransitionsEnabled()) {
                 requestChangeTransitionIfNeeded(changes, null /* displayChange */);
-            } else {
+            } else if (mLastHasContent) {
                 mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */, this);
             }
             sendNewConfiguration();
@@ -1702,8 +1701,7 @@
             case SOFT_INPUT_STATE_HIDDEN:
                 return false;
         }
-        return r.mLastImeShown && mInputMethodWindow != null && mInputMethodWindow.mHasSurface
-                && mInputMethodWindow.mViewVisibility == View.VISIBLE;
+        return r.mLastImeShown;
     }
 
     /** Returns {@code true} if the top activity is transformed with the new rotation of display. */
@@ -3222,6 +3220,7 @@
      */
     void requestChangeTransitionIfNeeded(@ActivityInfo.Config int changes,
             @Nullable TransitionRequestInfo.DisplayChange displayChange) {
+        if (!mLastHasContent) return;
         final TransitionController controller = mTransitionController;
         if (controller.isCollecting()) {
             if (displayChange != null) {
@@ -5090,6 +5089,11 @@
         return mLastHasContent;
     }
 
+    @VisibleForTesting
+    void setLastHasContent() {
+        mLastHasContent = true;
+    }
+
     void registerPointerEventListener(@NonNull PointerEventListener listener) {
         mPointerEventDispatcher.registerInputEventListener(listener);
     }
@@ -6460,9 +6464,8 @@
 
     DisplayArea findAreaForWindowType(int windowType, Bundle options,
             boolean ownerCanManageAppToken, boolean roundedCornerOverlay) {
-        // TODO(b/159767464): figure out how to find an appropriate TDA.
         if (windowType >= FIRST_APPLICATION_WINDOW && windowType <= LAST_APPLICATION_WINDOW) {
-            return getDefaultTaskDisplayArea();
+            return mDisplayAreaPolicy.getTaskDisplayArea(options);
         }
         // Return IME container here because it could be in one of sub RootDisplayAreas depending on
         // the focused edit text. Also, the RootDisplayArea choosing strategy is implemented by
diff --git a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
index c85e04d..2cefd99 100644
--- a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
+++ b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
@@ -256,7 +256,7 @@
                     false /* applyFixedTransformationHint */);
             for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
                 final SurfaceControl leash = mTargetWindowTokens.valueAt(i);
-                if (leash != null) {
+                if (leash != null && leash.isValid()) {
                     rotator.applyTransform(t, leash);
                 }
             }
@@ -265,7 +265,7 @@
         // Hide the windows immediately because a screenshot layer should cover the screen.
         for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
             final SurfaceControl leash = mTargetWindowTokens.valueAt(i);
-            if (leash != null) {
+            if (leash != null && leash.isValid()) {
                 t.setAlpha(leash, 0f);
             }
         }
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index cbb473c..1955e30 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -175,7 +175,7 @@
      * Overrides corners raidus for activities presented in the letterbox mode. If given value < 0,
      * both it and a value of {@link
      * com.android.internal.R.integer.config_letterboxActivityCornersRadius} will be ignored and
-     * and corners of the activity won't be rounded.
+     * corners of the activity won't be rounded.
      */
     void setLetterboxActivityCornersRadius(int cornersRadius) {
         mLetterboxActivityCornersRadius = cornersRadius;
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 7d07357..8866343 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -150,7 +150,7 @@
             if (mLetterbox == null) {
                 mLetterbox = new Letterbox(() -> mActivityRecord.makeChildSurface(null),
                         mActivityRecord.mWmService.mTransactionFactory,
-                        mLetterboxConfiguration::isLetterboxActivityCornersRounded,
+                        this::shouldLetterboxHaveRoundedCorners,
                         this::getLetterboxBackgroundColor,
                         this::hasWallpaperBackgroudForLetterbox,
                         this::getLetterboxWallpaperBlurRadius,
@@ -175,6 +175,13 @@
         }
     }
 
+    private boolean shouldLetterboxHaveRoundedCorners() {
+        // TODO(b/214030873): remove once background is drawn for transparent activities
+        // Letterbox shouldn't have rounded corners if the activity is transparent
+        return mLetterboxConfiguration.isLetterboxActivityCornersRounded()
+                && mActivityRecord.fillsParent();
+    }
+
     float getHorizontalPositionMultiplier(Configuration parentConfiguration) {
         // Don't check resolved configuration because it may not be updated yet during
         // configuration change.
@@ -257,8 +264,6 @@
     @VisibleForTesting
     boolean shouldShowLetterboxUi(WindowState mainWindow) {
         return isSurfaceReadyAndVisible(mainWindow) && mainWindow.areAppWindowBoundsLetterboxed()
-                // Check that an activity isn't transparent.
-                && mActivityRecord.fillsParent()
                 // Check for FLAG_SHOW_WALLPAPER explicitly instead of using
                 // WindowContainer#showWallpaper because the later will return true when this
                 // activity is using blurred wallpaper for letterbox backgroud.
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index b91d3a8..5a420ca 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -983,29 +983,6 @@
             mWmService.checkDrawnWindowsLocked();
         }
 
-        final int N = mWmService.mPendingRemove.size();
-        if (N > 0) {
-            if (mWmService.mPendingRemoveTmp.length < N) {
-                mWmService.mPendingRemoveTmp = new WindowState[N + 10];
-            }
-            mWmService.mPendingRemove.toArray(mWmService.mPendingRemoveTmp);
-            mWmService.mPendingRemove.clear();
-            ArrayList<DisplayContent> displayList = new ArrayList();
-            for (i = 0; i < N; i++) {
-                final WindowState w = mWmService.mPendingRemoveTmp[i];
-                w.removeImmediately();
-                final DisplayContent displayContent = w.getDisplayContent();
-                if (displayContent != null && !displayList.contains(displayContent)) {
-                    displayList.add(displayContent);
-                }
-            }
-
-            for (int j = displayList.size() - 1; j >= 0; --j) {
-                final DisplayContent dc = displayList.get(j);
-                dc.assignWindowLayers(true /*setLayoutNeeded*/);
-            }
-        }
-
         forAllDisplays(dc -> {
             dc.getInputMonitor().updateInputWindowsLw(true /*force*/);
             dc.updateSystemGestureExclusion();
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index 5fe4076..eb73cd8 100644
--- a/services/core/java/com/android/server/wm/StartingSurfaceController.java
+++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java
@@ -34,6 +34,7 @@
 import android.util.Slog;
 import android.window.TaskSnapshot;
 
+import java.util.ArrayList;
 import java.util.function.Supplier;
 
 /**
@@ -45,6 +46,14 @@
     private final WindowManagerService mService;
     private final SplashScreenExceptionList mSplashScreenExceptionsList;
 
+    // Cache status while deferring add starting window
+    boolean mInitProcessRunning;
+    boolean mInitNewTask;
+    boolean mInitTaskSwitch;
+    private final ArrayList<DeferringStartingWindowRecord> mDeferringAddStartActivities =
+            new ArrayList<>();
+    private boolean mDeferringAddStartingWindow;
+
     public StartingSurfaceController(WindowManagerService wm) {
         mService = wm;
         mSplashScreenExceptionsList = new SplashScreenExceptionList(wm.mContext.getMainExecutor());
@@ -70,7 +79,7 @@
         return mSplashScreenExceptionsList.isException(packageName, targetSdk, infoProvider);
     }
 
-    int makeStartingWindowTypeParameter(boolean newTask, boolean taskSwitch,
+    static int makeStartingWindowTypeParameter(boolean newTask, boolean taskSwitch,
             boolean processRunning, boolean allowTaskSnapshot, boolean activityCreated,
             boolean useEmpty, boolean useLegacy, boolean activityDrawn) {
         int parameter = 0;
@@ -142,6 +151,82 @@
         }
     }
 
+    private static final class DeferringStartingWindowRecord {
+        final ActivityRecord mDeferring;
+        final ActivityRecord mPrev;
+        final ActivityRecord mSource;
+
+        DeferringStartingWindowRecord(ActivityRecord deferring, ActivityRecord prev,
+                ActivityRecord source) {
+            mDeferring = deferring;
+            mPrev = prev;
+            mSource = source;
+        }
+    }
+
+    /**
+     * Shows a starting window while starting a new activity. Do not use this method to create a
+     * starting window for an existing activity.
+     */
+    void showStartingWindow(ActivityRecord target, ActivityRecord prev,
+            boolean newTask, boolean isTaskSwitch, ActivityRecord source) {
+        if (mDeferringAddStartingWindow) {
+            addDeferringRecord(target, prev, newTask, isTaskSwitch, source);
+        } else {
+            target.showStartingWindow(prev, newTask, isTaskSwitch, true /* startActivity */,
+                    source);
+        }
+    }
+
+    /**
+     * Queueing the starting activity status while deferring add starting window.
+     * @see Task#startActivityLocked
+     */
+    private void addDeferringRecord(ActivityRecord deferring, ActivityRecord prev,
+            boolean newTask, boolean isTaskSwitch, ActivityRecord source) {
+        // Set newTask, taskSwitch, processRunning form first activity because those can change
+        // after first activity started.
+        if (mDeferringAddStartActivities.isEmpty()) {
+            mInitProcessRunning = deferring.isProcessRunning();
+            mInitNewTask = newTask;
+            mInitTaskSwitch = isTaskSwitch;
+        }
+        mDeferringAddStartActivities.add(new DeferringStartingWindowRecord(
+                deferring, prev, source));
+    }
+
+    private void showStartingWindowFromDeferringActivities() {
+        // Attempt to add starting window from the top-most activity.
+        for (int i = mDeferringAddStartActivities.size() - 1; i >= 0; --i) {
+            final DeferringStartingWindowRecord next = mDeferringAddStartActivities.get(i);
+            next.mDeferring.showStartingWindow(next.mPrev, mInitNewTask, mInitTaskSwitch,
+                    mInitProcessRunning, true /* startActivity */, next.mSource);
+            // If one succeeds, it is done.
+            if (next.mDeferring.mStartingData != null) {
+                break;
+            }
+        }
+        mDeferringAddStartActivities.clear();
+    }
+
+    /**
+     * Begin deferring add starting window in one pass.
+     * This is used to deferring add starting window while starting multiples activities because
+     * system only need to provide a starting window to the top-visible activity.
+     * Most call {@link #endDeferAddStartingWindow} when starting activities process finished.
+     * @see #endDeferAddStartingWindow()
+     */
+    void beginDeferAddStartingWindow() {
+        mDeferringAddStartingWindow = true;
+    }
+
+    /**
+     * End deferring add starting window.
+     */
+    void endDeferAddStartingWindow() {
+        mDeferringAddStartingWindow = false;
+        showStartingWindowFromDeferringActivities();
+    }
 
     final class StartingSurface {
         private final Task mTask;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7617726..43038ce 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5149,8 +5149,8 @@
 
                 final ActivityRecord prev = baseTask.getActivity(
                         a -> a.mStartingData != null && a.showToCurrentUser());
-                r.showStartingWindow(prev, newTask, isTaskSwitch,
-                        true /* startActivity */, sourceRecord);
+                mWmService.mStartingSurfaceController.showStartingWindow(r, prev, newTask,
+                        isTaskSwitch, sourceRecord);
             }
         } else {
             // If this is the first activity, don't do any fancy animations,
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 39a29103..c239e68 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -223,7 +223,6 @@
 import android.util.EventLog;
 import android.util.MergedConfiguration;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.TimeUtils;
 import android.util.TypedValue;
@@ -589,20 +588,6 @@
     final ArrayList<WindowState> mResizingWindows = new ArrayList<>();
 
     /**
-     * Windows whose animations have ended and now must be removed.
-     */
-    final ArrayList<WindowState> mPendingRemove = new ArrayList<>();
-
-    /**
-     * Used when processing mPendingRemove to avoid working on the original array.
-     */
-    WindowState[] mPendingRemoveTmp = new WindowState[20];
-
-    // TODO: use WindowProcessController once go/wm-unified is done.
-    /** Mapping of process pids to configurations */
-    final SparseArray<Configuration> mProcessConfigurations = new SparseArray<>();
-
-    /**
      * Windows whose surface should be destroyed.
      */
     final ArrayList<WindowState> mDestroySurface = new ArrayList<>();
@@ -2042,7 +2027,6 @@
             dc.mWinRemovedSinceNullFocus.add(win);
         }
         mEmbeddedWindowController.onWindowRemoved(win);
-        mPendingRemove.remove(win);
         mResizingWindows.remove(win);
         updateNonSystemOverlayWindowsVisibilityIfNeeded(win, false /* surfaceShown */);
         mWindowsChanged = true;
@@ -6346,23 +6330,6 @@
                 }
             }
         }
-        if (mPendingRemove.size() > 0) {
-            pw.println();
-            pw.println("  Remove pending for:");
-            for (int i=mPendingRemove.size()-1; i>=0; i--) {
-                WindowState w = mPendingRemove.get(i);
-                if (windows == null || windows.contains(w)) {
-                    pw.print("  Remove #"); pw.print(i); pw.print(' ');
-                            pw.print(w);
-                    if (dumpAll) {
-                        pw.println(":");
-                        w.dump(pw, "    ", true);
-                    } else {
-                        pw.println();
-                    }
-                }
-            }
-        }
         if (mForceRemoves != null && mForceRemoves.size() > 0) {
             pw.println();
             pw.println("  Windows force removing:");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6acd454..5bbe2cd 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -4835,15 +4835,20 @@
             if (hasSurface) {
                 mWmService.mDestroySurface.add(this);
             }
-            if (mRemoveOnExit) {
-                mWmService.mPendingRemove.add(this);
-                mRemoveOnExit = false;
-            }
         }
         mAnimatingExit = false;
         getDisplayContent().mWallpaperController.hideWallpapers(this);
     }
 
+    @Override
+    boolean handleCompleteDeferredRemoval() {
+        if (mRemoveOnExit) {
+            mRemoveOnExit = false;
+            removeImmediately();
+        }
+        return super.handleCompleteDeferredRemoval();
+    }
+
     boolean clearAnimatingFlags() {
         boolean didSomething = false;
         // We don't want to clear it out for windows that get replaced, because the
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 4190a91..94bc22a 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -59,6 +59,9 @@
 
 namespace android {
 
+static bool cancelRunningCompaction;
+static bool compactionInProgress;
+
 // Legacy method for compacting processes, any new code should
 // use compactProcess instead.
 static inline void compactProcessProcfs(int pid, const std::string& compactionType) {
@@ -83,9 +86,18 @@
         // Skip compaction if failed to open pidfd with any error
         return -errno;
     }
+    compactionInProgress = true;
+    cancelRunningCompaction = false;
 
     int64_t totalBytesCompacted = 0;
     for (int iBase = 0; iBase < vmas.size(); iBase += UIO_MAXIOV) {
+        if (CC_UNLIKELY(cancelRunningCompaction)) {
+            // There could be a significant delay betweenwhen a compaction
+            // is requested and when it is handled during this time
+            // our OOM adjust could have improved.
+            cancelRunningCompaction = false;
+            break;
+        }
         int totalVmasToKernel = std::min(UIO_MAXIOV, (int)(vmas.size() - iBase));
         for (int iVec = 0, iVma = iBase; iVec < totalVmasToKernel; ++iVec, ++iVma) {
             vmasToKernel[iVec].iov_base = (void*)vmas[iVma].start;
@@ -95,11 +107,13 @@
         auto bytesCompacted =
                 process_madvise(pidfd, vmasToKernel, totalVmasToKernel, madviseType, 0);
         if (CC_UNLIKELY(bytesCompacted == -1)) {
+            compactionInProgress = false;
             return -errno;
         }
 
         totalBytesCompacted += bytesCompacted;
     }
+    compactionInProgress = false;
 
     return totalBytesCompacted;
 }
@@ -228,6 +242,12 @@
     }
 }
 
+static void com_android_server_am_CachedAppOptimizer_cancelCompaction(JNIEnv*, jobject) {
+    if (compactionInProgress) {
+        cancelRunningCompaction = true;
+    }
+}
+
 static void com_android_server_am_CachedAppOptimizer_compactProcess(JNIEnv*, jobject, jint pid,
                                                                     jint compactionFlags) {
     compactProcessOrFallback(pid, compactionFlags);
@@ -279,6 +299,8 @@
 
 static const JNINativeMethod sMethods[] = {
         /* name, signature, funcPtr */
+        {"cancelCompaction", "()V",
+         (void*)com_android_server_am_CachedAppOptimizer_cancelCompaction},
         {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
         {"compactProcess", "(II)V", (void*)com_android_server_am_CachedAppOptimizer_compactProcess},
         {"freezeBinder", "(IZ)I", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index db8da11..6caf731 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -231,6 +231,7 @@
 import android.net.ConnectivityManager;
 import android.net.ConnectivitySettingsManager;
 import android.net.IIpConnectivityMetrics;
+import android.net.ProfileNetworkPreference;
 import android.net.ProxyInfo;
 import android.net.Uri;
 import android.net.VpnManager;
@@ -17827,10 +17828,14 @@
         }
         int networkPreference = preferentialNetworkServiceEnabled
                 ? PROFILE_NETWORK_PREFERENCE_ENTERPRISE : PROFILE_NETWORK_PREFERENCE_DEFAULT;
+        ProfileNetworkPreference.Builder preferenceBuilder =
+                new ProfileNetworkPreference.Builder();
+        preferenceBuilder.setPreference(networkPreference);
+        List<ProfileNetworkPreference> preferences = new ArrayList<>();
+        preferences.add(preferenceBuilder.build());
         mInjector.binderWithCleanCallingIdentity(() ->
-                mInjector.getConnectivityManager().setProfileNetworkPreference(
-                        UserHandle.of(userId),
-                        networkPreference,
+                mInjector.getConnectivityManager().setProfileNetworkPreferences(
+                        UserHandle.of(userId), preferences,
                         null /* executor */, null /* listener */));
     }
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 4673a69..5fcee9b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -310,6 +310,8 @@
             "com.android.clockwork.connectivity.WearConnectivityService";
     private static final String WEAR_POWER_SERVICE_CLASS =
             "com.android.clockwork.power.WearPowerService";
+    private static final String HEALTH_SERVICE_CLASS =
+            "com.google.android.clockwork.healthservices.HealthService";
     private static final String WEAR_SIDEKICK_SERVICE_CLASS =
             "com.google.android.clockwork.sidekick.SidekickService";
     private static final String WEAR_DISPLAYOFFLOAD_SERVICE_CLASS =
@@ -1454,13 +1456,18 @@
             ServiceManager.addService("scheduling_policy", new SchedulingPolicyService());
             t.traceEnd();
 
-            t.traceBegin("StartTelecomLoaderService");
-            mSystemServiceManager.startService(TelecomLoaderService.class);
-            t.traceEnd();
+            // TelecomLoader hooks into classes with defined HFP logic,
+            // so check for either telephony or microphone.
+            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE) ||
+                mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+                t.traceBegin("StartTelecomLoaderService");
+                mSystemServiceManager.startService(TelecomLoaderService.class);
+                t.traceEnd();
+            }
 
             t.traceBegin("StartTelephonyRegistry");
             telephonyRegistry = new TelephonyRegistry(
-                    context, new TelephonyRegistry.ConfigurationProvider());
+                context, new TelephonyRegistry.ConfigurationProvider());
             ServiceManager.addService("telephony.registry", telephonyRegistry);
             t.traceEnd();
 
@@ -1897,7 +1904,7 @@
 
             t.traceBegin("StartNetworkStatsService");
             try {
-                networkStats = NetworkStatsService.create(context, networkManagement);
+                networkStats = NetworkStatsService.create(context);
                 ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats);
             } catch (Throwable e) {
                 reportWtf("starting NetworkStats Service", e);
@@ -2494,6 +2501,10 @@
             mSystemServiceManager.startService(WEAR_POWER_SERVICE_CLASS);
             t.traceEnd();
 
+            t.traceBegin("StartHealthService");
+            mSystemServiceManager.startService(HEALTH_SERVICE_CLASS);
+            t.traceEnd();
+
             t.traceBegin("StartWearConnectivityService");
             mSystemServiceManager.startService(WEAR_CONNECTIVITY_SERVICE_CLASS);
             t.traceEnd();
@@ -2566,10 +2577,12 @@
             mActivityManagerService.enterSafeMode();
         }
 
-        // MMS service broker
-        t.traceBegin("StartMmsService");
-        mmsService = mSystemServiceManager.startService(MmsServiceBroker.class);
-        t.traceEnd();
+        if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            // MMS service broker
+            t.traceBegin("StartMmsService");
+            mmsService = mSystemServiceManager.startService(MmsServiceBroker.class);
+            t.traceEnd();
+        }
 
         if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOFILL)) {
             t.traceBegin("StartAutoFillService");
@@ -2985,9 +2998,7 @@
             t.traceEnd();
             t.traceBegin("MakeTelephonyRegistryReady");
             try {
-                if (telephonyRegistryF != null) {
-                    telephonyRegistryF.systemRunning();
-                }
+                if (telephonyRegistryF != null) telephonyRegistryF.systemRunning();
             } catch (Throwable e) {
                 reportWtf("Notifying TelephonyRegistry running", e);
             }
@@ -3001,15 +3012,15 @@
                 reportWtf("Notifying MediaRouterService running", e);
             }
             t.traceEnd();
-            t.traceBegin("MakeMmsServiceReady");
-            try {
-                if (mmsServiceF != null) {
-                    mmsServiceF.systemRunning();
+            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+                t.traceBegin("MakeMmsServiceReady");
+                try {
+                    if (mmsServiceF != null) mmsServiceF.systemRunning();
+                } catch (Throwable e) {
+                    reportWtf("Notifying MmsService running", e);
                 }
-            } catch (Throwable e) {
-                reportWtf("Notifying MmsService running", e);
+                t.traceEnd();
             }
-            t.traceEnd();
 
             t.traceBegin("IncidentDaemonReady");
             try {
@@ -3053,7 +3064,6 @@
     private void startApexServices(@NonNull TimingsTraceAndSlog t) {
         t.traceBegin("startApexServices");
         Map<String, String> services = ApexManager.getInstance().getApexSystemServices();
-        // TODO(satayev): filter out already started services
         // TODO(satayev): introduce android:order for services coming the same apexes
         for (String name : new TreeSet<>(services.keySet())) {
             String jarPath = services.get(name);
diff --git a/services/midi/OWNERS b/services/midi/OWNERS
new file mode 100644
index 0000000..f4d51f9
--- /dev/null
+++ b/services/midi/OWNERS
@@ -0,0 +1 @@
+philburk@google.com
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 8538603..635f136 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -40,26 +40,26 @@
     ],
 
     static_libs: [
+        "androidx.test.core",
+        "androidx.test.runner",
+        "androidx.test.ext.truth",
         "frameworks-base-testutils",
+        "hamcrest-library",
+        "kotlin-test",
+        "mockingservicestests-utils-mockito",
+        "mockito-target-extended-minus-junit4",
+        "platform-test-annotations",
+        "service-blobstore",
+        "service-jobscheduler",
+        "service-permission.impl",
         "services.core",
         "services.devicepolicy",
         "services.net",
         "services.usage",
-        "service-jobscheduler",
-        "service-permission.impl",
-        "service-blobstore",
-        "androidx.test.core",
-        "androidx.test.runner",
-        "androidx.test.ext.truth",
-        "mockito-target-extended-minus-junit4",
-        "platform-test-annotations",
-        "truth-prebuilt",
-        "hamcrest-library",
-        "servicestests-utils-mockito-extended",
-        "mockingservicestests-utils-mockito",
         "servicestests-core-utils",
+        "servicestests-utils-mockito-extended",
         "testables",
-        "kotlin-test",
+        "truth-prebuilt",
         // TODO: remove once Android migrates to JUnit 4.12, which provides assertThrows
         "testng",
     ],
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index 0c3e472..bdeb2b4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -122,15 +122,14 @@
                 .when(() -> LocalServices.getService(ActivityManagerInternal.class));
         doReturn(mock(AppStandbyInternal.class))
                 .when(() -> LocalServices.getService(AppStandbyInternal.class));
+        doReturn(mock(BatteryManagerInternal.class))
+                .when(() -> LocalServices.getService(BatteryManagerInternal.class));
         doReturn(mock(UsageStatsManagerInternal.class))
                 .when(() -> LocalServices.getService(UsageStatsManagerInternal.class));
         when(mContext.getString(anyInt())).thenReturn("some_test_string");
         // Called in BackgroundJobsController constructor.
         doReturn(mock(AppStateTrackerImpl.class))
                 .when(() -> LocalServices.getService(AppStateTracker.class));
-        // Called in BatteryController constructor.
-        doReturn(mock(BatteryManagerInternal.class))
-                .when(() -> LocalServices.getService(BatteryManagerInternal.class));
         // Called in ConnectivityController constructor.
         when(mContext.getSystemService(ConnectivityManager.class))
                 .thenReturn(mock(ConnectivityManager.class));
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index a9853bf..f61d6ca 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -48,18 +48,14 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.job.JobInfo;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.PackageManagerInternal;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkPolicyManager;
-import android.os.BatteryManager;
-import android.os.BatteryManagerInternal;
 import android.os.Build;
 import android.os.Looper;
 import android.os.SystemClock;
@@ -74,7 +70,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatchers;
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
@@ -88,8 +83,6 @@
     @Mock
     private Context mContext;
     @Mock
-    private BatteryManagerInternal mBatteryManagerInternal;
-    @Mock
     private ConnectivityManager mConnManager;
     @Mock
     private NetworkPolicyManager mNetPolicyManager;
@@ -115,9 +108,6 @@
         LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
         LocalServices.addService(NetworkPolicyManagerInternal.class, mNetPolicyManagerInternal);
 
-        LocalServices.removeServiceForTest(BatteryManagerInternal.class);
-        LocalServices.addService(BatteryManagerInternal.class, mBatteryManagerInternal);
-
         when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
 
         // Freeze the clocks at this moment in time
@@ -158,18 +148,10 @@
                 .setMinimumNetworkChunkBytes(DataUnit.KIBIBYTES.toBytes(100))
                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
 
-        final ArgumentCaptor<BroadcastReceiver> chargingCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
-        when(mBatteryManagerInternal.isPowered(eq(BatteryManager.BATTERY_PLUGGED_ANY)))
-                .thenReturn(false);
+        when(mService.isBatteryCharging()).thenReturn(false);
         final ConnectivityController controller = new ConnectivityController(mService);
-        verify(mContext).registerReceiver(chargingCaptor.capture(),
-                ArgumentMatchers.argThat(filter ->
-                        filter.hasAction(BatteryManager.ACTION_CHARGING)
-                                && filter.hasAction(BatteryManager.ACTION_DISCHARGING)));
         when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(10 * 60_000L);
-        final BroadcastReceiver chargingReceiver = chargingCaptor.getValue();
-        chargingReceiver.onReceive(mContext, new Intent(BatteryManager.ACTION_DISCHARGING));
+        controller.onBatteryStateChangedLocked();
 
         // Slow network is too slow
         assertFalse(controller.isSatisfied(createJobStatus(job), net,
@@ -225,17 +207,15 @@
                 createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(130)
                         .setLinkDownstreamBandwidthKbps(130).build(), mConstants));
         // Slow network is too slow, but device is charging and network is unmetered.
-        when(mBatteryManagerInternal.isPowered(eq(BatteryManager.BATTERY_PLUGGED_ANY)))
-                .thenReturn(true);
-        chargingReceiver.onReceive(mContext, new Intent(BatteryManager.ACTION_CHARGING));
+        when(mService.isBatteryCharging()).thenReturn(true);
+        controller.onBatteryStateChangedLocked();
         assertTrue(controller.isSatisfied(createJobStatus(job), net,
                 createCapabilitiesBuilder().addCapability(NET_CAPABILITY_NOT_METERED)
                         .setLinkUpstreamBandwidthKbps(1).setLinkDownstreamBandwidthKbps(1).build(),
                 mConstants));
 
-        when(mBatteryManagerInternal.isPowered(eq(BatteryManager.BATTERY_PLUGGED_ANY)))
-                .thenReturn(false);
-        chargingReceiver.onReceive(mContext, new Intent(BatteryManager.ACTION_DISCHARGING));
+        when(mService.isBatteryCharging()).thenReturn(false);
+        controller.onBatteryStateChangedLocked();
         when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(60_000L);
 
         // Slow network is too slow
@@ -259,9 +239,8 @@
                 createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(130)
                         .setLinkDownstreamBandwidthKbps(130).build(), mConstants));
         // Slow network is too slow, but device is charging and network is unmetered.
-        when(mBatteryManagerInternal.isPowered(eq(BatteryManager.BATTERY_PLUGGED_ANY)))
-                .thenReturn(true);
-        chargingReceiver.onReceive(mContext, new Intent(BatteryManager.ACTION_CHARGING));
+        when(mService.isBatteryCharging()).thenReturn(true);
+        controller.onBatteryStateChangedLocked();
         assertTrue(controller.isSatisfied(createJobStatus(job), net,
                 createCapabilitiesBuilder().addCapability(NET_CAPABILITY_NOT_METERED)
                         .setLinkUpstreamBandwidthKbps(1).setLinkDownstreamBandwidthKbps(1).build(),
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 300f93f..cfae9a3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -63,16 +63,13 @@
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManager;
 import android.app.usage.UsageStatsManagerInternal;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ServiceInfo;
-import android.os.BatteryManager;
 import android.os.BatteryManagerInternal;
 import android.os.Handler;
 import android.os.Looper;
@@ -126,7 +123,6 @@
     private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
     private static final int SOURCE_USER_ID = 0;
 
-    private BroadcastReceiver mChargingReceiver;
     private QuotaController mQuotaController;
     private QuotaController.QcConstants mQcConstants;
     private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants();
@@ -225,8 +221,6 @@
 
         // Initialize real objects.
         // Capture the listeners.
-        ArgumentCaptor<BroadcastReceiver> receiverCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
         ArgumentCaptor<IUidObserver> uidObserverCaptor =
                 ArgumentCaptor.forClass(IUidObserver.class);
         ArgumentCaptor<PowerAllowlistInternal.TempAllowlistChangeListener> taChangeCaptor =
@@ -236,11 +230,6 @@
         mQuotaController = new QuotaController(mJobSchedulerService,
                 mock(BackgroundJobsController.class), mock(ConnectivityController.class));
 
-        verify(mContext).registerReceiver(receiverCaptor.capture(),
-                ArgumentMatchers.argThat(filter ->
-                        filter.hasAction(BatteryManager.ACTION_CHARGING)
-                                && filter.hasAction(BatteryManager.ACTION_DISCHARGING)));
-        mChargingReceiver = receiverCaptor.getValue();
         verify(mPowerAllowlistInternal)
                 .registerTempAllowlistChangeListener(taChangeCaptor.capture());
         mTempAllowlistListener = taChangeCaptor.getValue();
@@ -280,13 +269,17 @@
     }
 
     private void setCharging() {
-        Intent intent = new Intent(BatteryManager.ACTION_CHARGING);
-        mChargingReceiver.onReceive(mContext, intent);
+        doReturn(true).when(mJobSchedulerService).isBatteryCharging();
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.onBatteryStateChangedLocked();
+        }
     }
 
     private void setDischarging() {
-        Intent intent = new Intent(BatteryManager.ACTION_DISCHARGING);
-        mChargingReceiver.onReceive(mContext, intent);
+        doReturn(false).when(mJobSchedulerService).isBatteryCharging();
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.onBatteryStateChangedLocked();
+        }
     }
 
     private void setProcessState(int procState) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
index f2415b4..bdfdf77 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -158,10 +158,10 @@
 
         mStagingManager.restoreSessions(Arrays.asList(session1, session2), true);
 
-        assertThat(session1.getErrorCode()).isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(session1.getErrorCode()).isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED);
         assertThat(session1.getErrorMessage()).isEqualTo("Build fingerprint has changed");
 
-        assertThat(session2.getErrorCode()).isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(session2.getErrorCode()).isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED);
         assertThat(session2.getErrorMessage()).isEqualTo("Build fingerprint has changed");
     }
 
@@ -247,12 +247,12 @@
         verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false));
 
         assertThat(apexSession.getErrorCode())
-                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+                .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED);
         assertThat(apexSession.getErrorMessage()).isEqualTo("apexd did not know anything about a "
                 + "staged session supposed to be activated");
 
         assertThat(apkSession.getErrorCode())
-                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+                .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED);
         assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed");
     }
 
@@ -303,22 +303,22 @@
         verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false));
 
         assertThat(apexSession1.getErrorCode())
-                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+                .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED);
         assertThat(apexSession1.getErrorMessage()).isEqualTo("APEX activation failed. "
                 + "Error: Failed for test");
 
         assertThat(apexSession2.getErrorCode())
-                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+                .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED);
         assertThat(apexSession2.getErrorMessage()).isEqualTo("Staged session 101 at boot didn't "
                 + "activate nor fail. Marking it as failed anyway.");
 
         assertThat(apexSession3.getErrorCode())
-                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+                .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED);
         assertThat(apexSession3.getErrorMessage()).isEqualTo("apexd did not know anything about a "
                 + "staged session supposed to be activated");
 
         assertThat(apkSession.getErrorCode())
-                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+                .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED);
         assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed");
     }
 
@@ -351,12 +351,12 @@
         verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false));
 
         assertThat(apexSession.getErrorCode())
-                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+                .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED);
         assertThat(apexSession.getErrorMessage()).isEqualTo("Staged session 1543 at boot didn't "
                 + "activate nor fail. Marking it as failed anyway.");
 
         assertThat(apkSession.getErrorCode())
-                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+                .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED);
         assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed");
     }
 
@@ -445,11 +445,11 @@
         verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false));
 
         assertThat(apexSession.getErrorCode())
-                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+                .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED);
         assertThat(apexSession.getErrorMessage()).isEqualTo("Impossible state");
 
         assertThat(apkSession.getErrorCode())
-                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+                .isEqualTo(SessionInfo.SESSION_ACTIVATION_FAILED);
         assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed");
     }
 
@@ -754,7 +754,7 @@
                 /* isReady */ false,
                 /* isFailed */ false,
                 /* isApplied */false,
-                /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.STAGED_SESSION_NO_ERROR,
+                /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.SESSION_NO_ERROR,
                 /* stagedSessionErrorMessage */ "no error");
 
         StagingManager.StagedSession stagedSession = spy(session.mStagedSession);
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java
new file mode 100644
index 0000000..b65c3e9
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java
@@ -0,0 +1,303 @@
+/*
+ * 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.power;
+
+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.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManagerInternal;
+import android.attention.AttentionManagerInternal;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.Resources;
+import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.BatteryManagerInternal;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.PowerSaveState;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.service.dreams.DreamManagerInternal;
+import android.test.mock.MockContentResolver;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.lights.LightsManager;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.power.PowerManagerService.BatteryReceiver;
+import com.android.server.power.PowerManagerService.Injector;
+import com.android.server.power.PowerManagerService.NativeWrapper;
+import com.android.server.power.PowerManagerService.UserSwitchedReceiver;
+import com.android.server.power.batterysaver.BatterySaverController;
+import com.android.server.power.batterysaver.BatterySaverPolicy;
+import com.android.server.power.batterysaver.BatterySaverStateMachine;
+import com.android.server.power.batterysaver.BatterySavingStats;
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link com.android.server.power.PowerManagerService}.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:PowerManagerServiceMockingTest
+ */
+public class PowerManagerServiceMockingTest {
+    private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent";
+    private static final String SYSTEM_PROPERTY_REBOOT_REASON = "sys.boot.reason";
+
+    private static final float BRIGHTNESS_FACTOR = 0.7f;
+    private static final boolean BATTERY_SAVER_ENABLED = true;
+
+    @Mock private BatterySaverController mBatterySaverControllerMock;
+    @Mock private BatterySaverPolicy mBatterySaverPolicyMock;
+    @Mock private BatterySaverStateMachine mBatterySaverStateMachineMock;
+    @Mock private LightsManager mLightsManagerMock;
+    @Mock private DisplayManagerInternal mDisplayManagerInternalMock;
+    @Mock private BatteryManagerInternal mBatteryManagerInternalMock;
+    @Mock private ActivityManagerInternal mActivityManagerInternalMock;
+    @Mock private AttentionManagerInternal mAttentionManagerInternalMock;
+    @Mock private DreamManagerInternal mDreamManagerInternalMock;
+    @Mock private PowerManagerService.NativeWrapper mNativeWrapperMock;
+    @Mock private Notifier mNotifierMock;
+    @Mock private WirelessChargerDetector mWirelessChargerDetectorMock;
+    @Mock private AmbientDisplayConfiguration mAmbientDisplayConfigurationMock;
+    @Mock private SystemPropertiesWrapper mSystemPropertiesMock;
+    @Mock private DeviceStateManager mDeviceStateManagerMock;
+
+    @Mock
+    private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
+
+    private PowerManagerService mService;
+    private PowerSaveState mPowerSaveState;
+    private ContextWrapper mContextSpy;
+    private BatteryReceiver mBatteryReceiver;
+    private UserSwitchedReceiver mUserSwitchedReceiver;
+    private Resources mResourcesSpy;
+    private OffsettableClock mClock;
+    private TestLooper mTestLooper;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        FakeSettingsProvider.clearSettingsProvider();
+
+        mPowerSaveState = new PowerSaveState.Builder()
+                .setBatterySaverEnabled(BATTERY_SAVER_ENABLED)
+                .setBrightnessFactor(BRIGHTNESS_FACTOR)
+                .build();
+        when(mBatterySaverPolicyMock.getBatterySaverPolicy(
+                eq(PowerManager.ServiceType.SCREEN_BRIGHTNESS)))
+                .thenReturn(mPowerSaveState);
+        when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(false);
+        when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(false);
+        when(mDisplayManagerInternalMock.requestPowerState(anyInt(), any(), anyBoolean()))
+                .thenReturn(true);
+        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn("");
+        when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true);
+
+        addLocalServiceMock(LightsManager.class, mLightsManagerMock);
+        addLocalServiceMock(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+        addLocalServiceMock(BatteryManagerInternal.class, mBatteryManagerInternalMock);
+        addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
+        addLocalServiceMock(AttentionManagerInternal.class, mAttentionManagerInternalMock);
+        addLocalServiceMock(DreamManagerInternal.class, mDreamManagerInternalMock);
+
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+        mResourcesSpy = spy(mContextSpy.getResources());
+        when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
+
+        MockContentResolver cr = new MockContentResolver(mContextSpy);
+        cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+        when(mContextSpy.getContentResolver()).thenReturn(cr);
+
+        when(mContextSpy.getSystemService(DeviceStateManager.class))
+                .thenReturn(mDeviceStateManagerMock);
+
+        Settings.Global.putInt(mContextSpy.getContentResolver(),
+                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
+
+        mClock = new OffsettableClock.Stopped();
+        mTestLooper = new TestLooper(mClock::now);
+    }
+
+    private PowerManagerService createService() {
+        mService = new PowerManagerService(mContextSpy, new Injector() {
+            @Override
+            Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
+                    SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
+                    FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector) {
+                return mNotifierMock;
+            }
+
+            @Override
+            SuspendBlocker createSuspendBlocker(PowerManagerService service, String name) {
+                return super.createSuspendBlocker(service, name);
+            }
+
+            @Override
+            BatterySaverPolicy createBatterySaverPolicy(
+                    Object lock, Context context, BatterySavingStats batterySavingStats) {
+                return mBatterySaverPolicyMock;
+            }
+
+            @Override
+            BatterySaverController createBatterySaverController(
+                    Object lock, Context context, BatterySaverPolicy batterySaverPolicy,
+                    BatterySavingStats batterySavingStats) {
+                return mBatterySaverControllerMock;
+            }
+
+            @Override
+            BatterySaverStateMachine createBatterySaverStateMachine(Object lock, Context context,
+                    BatterySaverController batterySaverController) {
+                return mBatterySaverStateMachineMock;
+            }
+
+            @Override
+            NativeWrapper createNativeWrapper() {
+                return mNativeWrapperMock;
+            }
+
+            @Override
+            WirelessChargerDetector createWirelessChargerDetector(
+                    SensorManager sensorManager, SuspendBlocker suspendBlocker, Handler handler) {
+                return mWirelessChargerDetectorMock;
+            }
+
+            @Override
+            AmbientDisplayConfiguration createAmbientDisplayConfiguration(Context context) {
+                return mAmbientDisplayConfigurationMock;
+            }
+
+            @Override
+            InattentiveSleepWarningController createInattentiveSleepWarningController() {
+                return mInattentiveSleepWarningControllerMock;
+            }
+
+            @Override
+            public SystemPropertiesWrapper createSystemPropertiesWrapper() {
+                return mSystemPropertiesMock;
+            }
+
+            @Override
+            PowerManagerService.Clock createClock() {
+                return () -> mClock.now();
+            }
+
+            @Override
+            Handler createHandler(Looper looper, Handler.Callback callback) {
+                return new Handler(mTestLooper.getLooper(), callback);
+            }
+
+            @Override
+            void invalidateIsInteractiveCaches() {
+                // Avoids an SELinux failure.
+            }
+        });
+        return mService;
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        LocalServices.removeServiceForTest(LightsManager.class);
+        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+        LocalServices.removeServiceForTest(BatteryManagerInternal.class);
+        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+        LocalServices.removeServiceForTest(AttentionManagerInternal.class);
+        LocalServices.removeServiceForTest(DreamManagerInternal.class);
+        FakeSettingsProvider.clearSettingsProvider();
+    }
+
+    /**
+     * Creates a mock and registers it to {@link LocalServices}.
+     */
+    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+        LocalServices.removeServiceForTest(clazz);
+        LocalServices.addService(clazz, mock);
+    }
+
+    private void advanceTime(long timeMs) {
+        mClock.fastForward(timeMs);
+        mTestLooper.dispatchAll();
+    }
+
+    @Test
+    public void testUserActivityOnDeviceStateChange() {
+        createService();
+        mService.systemReady(null);
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
+
+        final ArgumentCaptor<DeviceStateCallback> deviceStateCallbackCaptor =
+                ArgumentCaptor.forClass(DeviceStateCallback.class);
+        verify(mDeviceStateManagerMock).registerCallback(any(),
+                deviceStateCallbackCaptor.capture());
+
+        // Advance the time 10001 and verify that the device thinks it has been idle
+        // for just less than that.
+        mService.onUserActivity();
+        advanceTime(10001);
+        assertThat(mService.wasDeviceIdleForInternal(10000)).isTrue();
+
+        // Send a display state change event and advance the clock 10.
+        final DeviceStateCallback deviceStateCallback = deviceStateCallbackCaptor.getValue();
+        deviceStateCallback.onStateChanged(1);
+        final long timeToAdvance = 10;
+        advanceTime(timeToAdvance);
+
+        // Ensure that the device has been idle for only 10 (doesn't include the idle time
+        // before the display state event).
+        assertThat(mService.wasDeviceIdleForInternal(timeToAdvance - 1)).isTrue();
+        assertThat(mService.wasDeviceIdleForInternal(timeToAdvance)).isFalse();
+
+        // Send the same state and ensure that does not trigger an update.
+        deviceStateCallback.onStateChanged(1);
+        advanceTime(timeToAdvance);
+        final long newTime = timeToAdvance * 2;
+
+        assertThat(mService.wasDeviceIdleForInternal(newTime - 1)).isTrue();
+        assertThat(mService.wasDeviceIdleForInternal(newTime)).isFalse();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/SystemServiceManagerTest.java b/services/tests/servicestests/src/com/android/server/SystemServiceManagerTest.java
index 4413dc8..f92f5ea 100644
--- a/services/tests/servicestests/src/com/android/server/SystemServiceManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/SystemServiceManagerTest.java
@@ -23,6 +23,7 @@
 import org.junit.Test;
 
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 
 
 /**
@@ -32,24 +33,52 @@
 
     private static final String TAG = "SystemServiceManagerTest";
 
+    private final SystemServiceManager mSystemServiceManager =
+            new SystemServiceManager(getContext());
+
     @Test
     public void testSealStartedServices() throws Exception {
-        SystemServiceManager manager = new SystemServiceManager(getContext());
         // must be effectively final, since it's changed from inner class below
         AtomicBoolean serviceStarted = new AtomicBoolean(false);
-        SystemService service = new SystemService(getContext()) {
+        SystemService service1 = new SystemService(getContext()) {
             @Override
             public void onStart() {
                 serviceStarted.set(true);
             }
         };
+        SystemService service2 = new SystemService(getContext()) {
+            @Override
+            public void onStart() {
+                throw new IllegalStateException("Second service must not be called");
+            }
+        };
 
         // started services have their #onStart methods called
-        manager.startService(service);
+        mSystemServiceManager.startService(service1);
         assertTrue(serviceStarted.get());
 
         // however, after locking started services, it is not possible to start a new service
-        manager.sealStartedServices();
-        assertThrows(UnsupportedOperationException.class, () -> manager.startService(service));
+        mSystemServiceManager.sealStartedServices();
+        assertThrows(UnsupportedOperationException.class,
+                () -> mSystemServiceManager.startService(service2));
     }
+
+    @Test
+    public void testDuplicateServices() throws Exception {
+        AtomicInteger counter = new AtomicInteger(0);
+        SystemService service = new SystemService(getContext()) {
+            @Override
+            public void onStart() {
+                counter.incrementAndGet();
+            }
+        };
+
+        mSystemServiceManager.startService(service);
+        assertEquals(1, counter.get());
+
+        // manager does not start the same service twice
+        mSystemServiceManager.startService(service);
+        assertEquals(1, counter.get());
+    }
+
 }
diff --git a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
index cffff66..02cf971 100644
--- a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
@@ -23,7 +23,14 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.debug.AdbManager;
+import android.debug.IAdbManager;
+import android.os.ServiceManager;
 import android.provider.Settings;
 import android.util.Log;
 
@@ -105,6 +112,7 @@
     public void tearDown() throws Exception {
         mKeyStore.deleteKeyStore();
         setAllowedConnectionTime(mOriginalAllowedConnectionTime);
+        dropShellPermissionIdentity();
     }
 
     /**
@@ -813,6 +821,108 @@
         return hasAtLeastOneLetter;
     }
 
+    CountDownLatch mAdbActionLatch = new CountDownLatch(1);
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            Log.i(TAG, "Received intent action=" + action);
+            if (AdbManager.WIRELESS_DEBUG_PAIRED_DEVICES_ACTION.equals(action)) {
+                assertEquals("Received broadcast without MANAGE_DEBUGGING permission.",
+                        context.checkSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING),
+                        PackageManager.PERMISSION_GRANTED);
+                Log.i(TAG, "action=" + action + " paired_device=" + intent.getSerializableExtra(
+                        AdbManager.WIRELESS_DEVICES_EXTRA).toString());
+                mAdbActionLatch.countDown();
+            } else if (AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION.equals(action)) {
+                assertEquals("Received broadcast without MANAGE_DEBUGGING permission.",
+                        context.checkSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING),
+                        PackageManager.PERMISSION_GRANTED);
+                int status = intent.getIntExtra(AdbManager.WIRELESS_STATUS_EXTRA,
+                        AdbManager.WIRELESS_STATUS_DISCONNECTED);
+                Log.i(TAG, "action=" + action + " status=" + status);
+                mAdbActionLatch.countDown();
+            } else if (AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION.equals(action)) {
+                assertEquals("Received broadcast without MANAGE_DEBUGGING permission.",
+                        context.checkSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING),
+                        PackageManager.PERMISSION_GRANTED);
+                Integer res = intent.getIntExtra(
+                        AdbManager.WIRELESS_STATUS_EXTRA,
+                        AdbManager.WIRELESS_STATUS_FAIL);
+                Log.i(TAG, "action=" + action + " result=" + res);
+
+                if (res.equals(AdbManager.WIRELESS_STATUS_PAIRING_CODE)) {
+                    String pairingCode = intent.getStringExtra(
+                                AdbManager.WIRELESS_PAIRING_CODE_EXTRA);
+                    Log.i(TAG, "pairingCode=" + pairingCode);
+                } else if (res.equals(AdbManager.WIRELESS_STATUS_CONNECTED)) {
+                    int port = intent.getIntExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, 0);
+                    Log.i(TAG, "port=" + port);
+                }
+                mAdbActionLatch.countDown();
+            }
+        }
+    };
+
+    private void adoptShellPermissionIdentity() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+            .adoptShellPermissionIdentity(android.Manifest.permission.MANAGE_DEBUGGING);
+    }
+
+    private void dropShellPermissionIdentity() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+            .dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void testBroadcastReceiverWithPermissions() throws Exception {
+        adoptShellPermissionIdentity();
+        final IAdbManager mAdbManager = IAdbManager.Stub.asInterface(
+                ServiceManager.getService(Context.ADB_SERVICE));
+        IntentFilter intentFilter =
+                new IntentFilter(AdbManager.WIRELESS_DEBUG_PAIRED_DEVICES_ACTION);
+        intentFilter.addAction(AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION);
+        intentFilter.addAction(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION);
+        assertEquals("Context does not have MANAGE_DEBUGGING permission.",
+                mContext.checkSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING),
+                PackageManager.PERMISSION_GRANTED);
+        try {
+            mContext.registerReceiver(mReceiver, intentFilter);
+            mAdbManager.enablePairingByPairingCode();
+            if (!mAdbActionLatch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) {
+                fail("Receiver did not receive adb intent action within the timeout duration");
+            }
+        } finally {
+            mContext.unregisterReceiver(mReceiver);
+        }
+    }
+
+    @Test
+    public void testBroadcastReceiverWithoutPermissions() throws Exception {
+        adoptShellPermissionIdentity();
+        final IAdbManager mAdbManager = IAdbManager.Stub.asInterface(
+                ServiceManager.getService(Context.ADB_SERVICE));
+        IntentFilter intentFilter =
+                new IntentFilter(AdbManager.WIRELESS_DEBUG_PAIRED_DEVICES_ACTION);
+        intentFilter.addAction(AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION);
+        intentFilter.addAction(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION);
+        mAdbManager.enablePairingByPairingCode();
+
+        dropShellPermissionIdentity();
+        assertEquals("Context has MANAGE_DEBUGGING permission.",
+                mContext.checkSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING),
+                PackageManager.PERMISSION_DENIED);
+        try {
+            mContext.registerReceiver(mReceiver, intentFilter);
+
+            if (mAdbActionLatch.await(TIMEOUT, TIMEOUT_TIME_UNIT)) {
+                fail("Broadcast receiver received adb action intent without debug permissions");
+            }
+        } finally {
+            mContext.unregisterReceiver(mReceiver);
+        }
+    }
+
     /**
      * Runs an adb test with the provided configuration.
      *
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
deleted file mode 100644
index d4bac2c..0000000
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics.sensors;
-
-import static android.testing.TestableLooper.RunWithLooper;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyBoolean;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-
-import android.os.Handler;
-import android.platform.test.annotations.Presubmit;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@Presubmit
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper(setAsMainLooper = true)
-@SmallTest
-public class BiometricSchedulerOperationTest {
-
-    public interface FakeHal {}
-    public abstract static class InterruptableMonitor<T>
-            extends HalClientMonitor<T> implements  Interruptable {
-        public InterruptableMonitor() {
-            super(null, null, null, null, 0, null, 0, 0, 0, 0, 0);
-        }
-    }
-
-    @Mock
-    private InterruptableMonitor<FakeHal> mClientMonitor;
-    @Mock
-    private BaseClientMonitor.Callback mClientCallback;
-    @Mock
-    private FakeHal mHal;
-    @Captor
-    ArgumentCaptor<BaseClientMonitor.Callback> mStartCallback;
-
-    private Handler mHandler;
-    private BiometricSchedulerOperation mOperation;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mHandler = new Handler(TestableLooper.get(this).getLooper());
-        mOperation = new BiometricSchedulerOperation(mClientMonitor, mClientCallback);
-    }
-
-    @Test
-    public void testStartWithCookie() {
-        final int cookie = 200;
-        when(mClientMonitor.getCookie()).thenReturn(cookie);
-        when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
-
-        assertThat(mOperation.isReadyToStart()).isEqualTo(cookie);
-        assertThat(mOperation.isStarted()).isFalse();
-        assertThat(mOperation.isCanceling()).isFalse();
-        assertThat(mOperation.isFinished()).isFalse();
-
-        final boolean started = mOperation.startWithCookie(
-                mock(BaseClientMonitor.Callback.class), cookie);
-
-        assertThat(started).isTrue();
-        verify(mClientMonitor).start(mStartCallback.capture());
-        mStartCallback.getValue().onClientStarted(mClientMonitor);
-        assertThat(mOperation.isStarted()).isTrue();
-    }
-
-    @Test
-    public void testNoStartWithoutCookie() {
-        final int goodCookie = 20;
-        final int badCookie = 22;
-        when(mClientMonitor.getCookie()).thenReturn(goodCookie);
-        when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
-
-        assertThat(mOperation.isReadyToStart()).isEqualTo(goodCookie);
-        final boolean started = mOperation.startWithCookie(
-                mock(BaseClientMonitor.Callback.class), badCookie);
-
-        assertThat(started).isFalse();
-        assertThat(mOperation.isStarted()).isFalse();
-        assertThat(mOperation.isCanceling()).isFalse();
-        assertThat(mOperation.isFinished()).isFalse();
-    }
-
-    @Test
-    public void startsWhenReadyAndHalAvailable() {
-        when(mClientMonitor.getCookie()).thenReturn(0);
-        when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
-
-        final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class);
-        mOperation.start(cb);
-        verify(mClientMonitor).start(mStartCallback.capture());
-        mStartCallback.getValue().onClientStarted(mClientMonitor);
-
-        assertThat(mOperation.isStarted()).isTrue();
-        assertThat(mOperation.isCanceling()).isFalse();
-        assertThat(mOperation.isFinished()).isFalse();
-
-        verify(mClientCallback).onClientStarted(eq(mClientMonitor));
-        verify(cb).onClientStarted(eq(mClientMonitor));
-        verify(mClientCallback, never()).onClientFinished(any(), anyBoolean());
-        verify(cb, never()).onClientFinished(any(), anyBoolean());
-
-        mStartCallback.getValue().onClientFinished(mClientMonitor, true);
-
-        assertThat(mOperation.isFinished()).isTrue();
-        assertThat(mOperation.isCanceling()).isFalse();
-        verify(mClientMonitor).destroy();
-        verify(cb).onClientFinished(eq(mClientMonitor), eq(true));
-    }
-
-    @Test
-    public void startFailsWhenReadyButHalNotAvailable() {
-        when(mClientMonitor.getCookie()).thenReturn(0);
-        when(mClientMonitor.getFreshDaemon()).thenReturn(null);
-
-        final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class);
-        mOperation.start(cb);
-        verify(mClientMonitor, never()).start(any());
-
-        assertThat(mOperation.isStarted()).isFalse();
-        assertThat(mOperation.isCanceling()).isFalse();
-        assertThat(mOperation.isFinished()).isTrue();
-
-        verify(mClientCallback, never()).onClientStarted(eq(mClientMonitor));
-        verify(cb, never()).onClientStarted(eq(mClientMonitor));
-        verify(mClientCallback).onClientFinished(eq(mClientMonitor), eq(false));
-        verify(cb).onClientFinished(eq(mClientMonitor), eq(false));
-    }
-
-    @Test
-    public void doesNotStartWithCookie() {
-        when(mClientMonitor.getCookie()).thenReturn(9);
-        assertThrows(IllegalStateException.class,
-                () -> mOperation.start(mock(BaseClientMonitor.Callback.class)));
-    }
-
-    @Test
-    public void cannotRestart() {
-        when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
-
-        mOperation.start(mock(BaseClientMonitor.Callback.class));
-
-        assertThrows(IllegalStateException.class,
-                () -> mOperation.start(mock(BaseClientMonitor.Callback.class)));
-    }
-
-    @Test
-    public void abortsNotRunning() {
-        when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
-
-        mOperation.abort();
-
-        assertThat(mOperation.isFinished()).isTrue();
-        verify(mClientMonitor).unableToStart();
-        verify(mClientMonitor).destroy();
-        assertThrows(IllegalStateException.class,
-                () -> mOperation.start(mock(BaseClientMonitor.Callback.class)));
-    }
-
-    @Test
-    public void cannotAbortRunning() {
-        when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
-
-        mOperation.start(mock(BaseClientMonitor.Callback.class));
-
-        assertThrows(IllegalStateException.class, () -> mOperation.abort());
-    }
-
-    @Test
-    public void cancel() {
-        when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
-
-        final BaseClientMonitor.Callback startCb = mock(BaseClientMonitor.Callback.class);
-        final BaseClientMonitor.Callback cancelCb = mock(BaseClientMonitor.Callback.class);
-        mOperation.start(startCb);
-        verify(mClientMonitor).start(mStartCallback.capture());
-        mStartCallback.getValue().onClientStarted(mClientMonitor);
-        mOperation.cancel(mHandler, cancelCb);
-
-        assertThat(mOperation.isCanceling()).isTrue();
-        verify(mClientMonitor).cancel();
-        verify(mClientMonitor, never()).cancelWithoutStarting(any());
-        verify(mClientMonitor, never()).destroy();
-
-        mStartCallback.getValue().onClientFinished(mClientMonitor, true);
-
-        assertThat(mOperation.isFinished()).isTrue();
-        assertThat(mOperation.isCanceling()).isFalse();
-        verify(mClientMonitor).destroy();
-
-        // should be unused since the operation was started
-        verify(cancelCb, never()).onClientStarted(any());
-        verify(cancelCb, never()).onClientFinished(any(), anyBoolean());
-    }
-
-    @Test
-    public void cancelWithoutStarting() {
-        when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
-
-        final BaseClientMonitor.Callback cancelCb = mock(BaseClientMonitor.Callback.class);
-        mOperation.cancel(mHandler, cancelCb);
-
-        assertThat(mOperation.isCanceling()).isTrue();
-        ArgumentCaptor<BaseClientMonitor.Callback> cbCaptor =
-                ArgumentCaptor.forClass(BaseClientMonitor.Callback.class);
-        verify(mClientMonitor).cancelWithoutStarting(cbCaptor.capture());
-
-        cbCaptor.getValue().onClientFinished(mClientMonitor, true);
-        verify(cancelCb).onClientFinished(eq(mClientMonitor), eq(true));
-        verify(mClientMonitor, never()).start(any());
-        verify(mClientMonitor, never()).cancel();
-        verify(mClientMonitor).destroy();
-    }
-
-    @Test
-    public void markCanceling() {
-        when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
-
-        mOperation.markCanceling();
-
-        assertThat(mOperation.isMarkedCanceling()).isTrue();
-        assertThat(mOperation.isCanceling()).isFalse();
-        assertThat(mOperation.isFinished()).isFalse();
-        verify(mClientMonitor, never()).start(any());
-        verify(mClientMonitor, never()).cancel();
-        verify(mClientMonitor, never()).cancelWithoutStarting(any());
-        verify(mClientMonitor, never()).unableToStart();
-        verify(mClientMonitor, never()).destroy();
-    }
-
-    @Test
-    public void cancelPendingWithCookie() {
-        markCancellingAndStart(2);
-    }
-
-    @Test
-    public void cancelPendingWithoutCookie() {
-        markCancellingAndStart(null);
-    }
-
-    private void markCancellingAndStart(Integer withCookie) {
-        when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
-        if (withCookie != null) {
-            when(mClientMonitor.getCookie()).thenReturn(withCookie);
-        }
-
-        mOperation.markCanceling();
-        final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class);
-        if (withCookie != null) {
-            mOperation.startWithCookie(cb, withCookie);
-        } else {
-            mOperation.start(cb);
-        }
-
-        assertThat(mOperation.isFinished()).isTrue();
-        verify(cb).onClientFinished(eq(mClientMonitor), eq(true));
-        verify(mClientMonitor, never()).start(any());
-        verify(mClientMonitor, never()).cancel();
-        verify(mClientMonitor, never()).cancelWithoutStarting(any());
-        verify(mClientMonitor, never()).unableToStart();
-        verify(mClientMonitor).destroy();
-    }
-
-    @Test
-    public void cancelWatchdogWhenStarted() {
-        cancelWatchdog(true);
-    }
-
-    @Test
-    public void cancelWatchdogWithoutStarting() {
-        cancelWatchdog(false);
-    }
-
-    private void cancelWatchdog(boolean start) {
-        when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
-
-        mOperation.start(mock(BaseClientMonitor.Callback.class));
-        if (start) {
-            verify(mClientMonitor).start(mStartCallback.capture());
-            mStartCallback.getValue().onClientStarted(mClientMonitor);
-        }
-        mOperation.cancel(mHandler, mock(BaseClientMonitor.Callback.class));
-
-        assertThat(mOperation.isCanceling()).isTrue();
-
-        // omit call to onClientFinished and trigger watchdog
-        mOperation.mCancelWatchdog.run();
-
-        assertThat(mOperation.isFinished()).isTrue();
-        assertThat(mOperation.isCanceling()).isFalse();
-        verify(mClientMonitor).destroy();
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index ac08319..d192697 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -16,14 +16,10 @@
 
 package com.android.server.biometrics.sensors;
 
-import static android.testing.TestableLooper.RunWithLooper;
-
 import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -38,13 +34,10 @@
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.IBiometricService;
 import android.os.Binder;
-import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
-import android.testing.TestableLooper;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -53,18 +46,16 @@
 
 import com.android.server.biometrics.nano.BiometricSchedulerProto;
 import com.android.server.biometrics.nano.BiometricsProto;
+import com.android.server.biometrics.sensors.BiometricScheduler.Operation;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 @Presubmit
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper(setAsMainLooper = true)
 public class BiometricSchedulerTest {
 
     private static final String TAG = "BiometricSchedulerTest";
@@ -85,9 +76,8 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mToken = new Binder();
-        mScheduler = new BiometricScheduler(TAG, new Handler(TestableLooper.get(this).getLooper()),
-                BiometricScheduler.SENSOR_TYPE_UNKNOWN, null /* gestureAvailabilityTracker */,
-                mBiometricService, LOG_NUM_RECENT_OPERATIONS,
+        mScheduler = new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_UNKNOWN,
+                null /* gestureAvailabilityTracker */, mBiometricService, LOG_NUM_RECENT_OPERATIONS,
                 CoexCoordinator.getInstance());
     }
 
@@ -96,9 +86,9 @@
         final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class);
 
         final HalClientMonitor<Object> client1 =
-                new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
+                new TestClientMonitor(mContext, mToken, nonNullDaemon);
         final HalClientMonitor<Object> client2 =
-                new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
+                new TestClientMonitor(mContext, mToken, nonNullDaemon);
         mScheduler.scheduleClientMonitor(client1);
         mScheduler.scheduleClientMonitor(client2);
 
@@ -109,17 +99,20 @@
     @Test
     public void testRemovesPendingOperations_whenNullHal_andNotBiometricPrompt() {
         // Even if second client has a non-null daemon, it needs to be canceled.
-        final TestHalClientMonitor client1 = new TestHalClientMonitor(
-                mContext, mToken, () -> null);
-        final TestHalClientMonitor client2 = new TestHalClientMonitor(
-                mContext, mToken, () -> mock(Object.class));
+        Object daemon2 = mock(Object.class);
+
+        final HalClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> null;
+        final HalClientMonitor.LazyDaemon<Object> lazyDaemon2 = () -> daemon2;
+
+        final TestClientMonitor client1 = new TestClientMonitor(mContext, mToken, lazyDaemon1);
+        final TestClientMonitor client2 = new TestClientMonitor(mContext, mToken, lazyDaemon2);
 
         final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
         final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class);
 
         // Pretend the scheduler is busy so the first operation doesn't start right away. We want
         // to pretend like there are two operations in the queue before kicking things off
-        mScheduler.mCurrentOperation = new BiometricSchedulerOperation(
+        mScheduler.mCurrentOperation = new BiometricScheduler.Operation(
                 mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class));
 
         mScheduler.scheduleClientMonitor(client1, callback1);
@@ -129,11 +122,11 @@
         mScheduler.scheduleClientMonitor(client2, callback2);
         waitForIdle();
 
-        assertTrue(client1.mUnableToStart);
+        assertTrue(client1.wasUnableToStart());
         verify(callback1).onClientFinished(eq(client1), eq(false) /* success */);
         verify(callback1, never()).onClientStarted(any());
 
-        assertTrue(client2.mUnableToStart);
+        assertTrue(client2.wasUnableToStart());
         verify(callback2).onClientFinished(eq(client2), eq(false) /* success */);
         verify(callback2, never()).onClientStarted(any());
 
@@ -145,19 +138,21 @@
         // Second non-BiometricPrompt client has a valid daemon
         final Object daemon2 = mock(Object.class);
 
+        final HalClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> null;
+        final HalClientMonitor.LazyDaemon<Object> lazyDaemon2 = () -> daemon2;
+
         final ClientMonitorCallbackConverter listener1 = mock(ClientMonitorCallbackConverter.class);
 
         final TestAuthenticationClient client1 =
-                new TestAuthenticationClient(mContext, () -> null, mToken, listener1);
-        final TestHalClientMonitor client2 =
-                new TestHalClientMonitor(mContext, mToken, () -> daemon2);
+                new TestAuthenticationClient(mContext, lazyDaemon1, mToken, listener1);
+        final TestClientMonitor client2 = new TestClientMonitor(mContext, mToken, lazyDaemon2);
 
         final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
         final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class);
 
         // Pretend the scheduler is busy so the first operation doesn't start right away. We want
         // to pretend like there are two operations in the queue before kicking things off
-        mScheduler.mCurrentOperation = new BiometricSchedulerOperation(
+        mScheduler.mCurrentOperation = new BiometricScheduler.Operation(
                 mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class));
 
         mScheduler.scheduleClientMonitor(client1, callback1);
@@ -177,8 +172,8 @@
         verify(callback1, never()).onClientStarted(any());
 
         // Client 2 was able to start
-        assertFalse(client2.mUnableToStart);
-        assertTrue(client2.mStarted);
+        assertFalse(client2.wasUnableToStart());
+        assertTrue(client2.hasStarted());
         verify(callback2).onClientStarted(eq(client2));
     }
 
@@ -192,18 +187,16 @@
         // Schedule a BiometricPrompt authentication request
         mScheduler.scheduleClientMonitor(client1, callback1);
 
-        assertNotEquals(0, mScheduler.mCurrentOperation.isReadyToStart());
-        assertEquals(client1, mScheduler.mCurrentOperation.getClientMonitor());
+        assertEquals(Operation.STATE_WAITING_FOR_COOKIE, mScheduler.mCurrentOperation.mState);
+        assertEquals(client1, mScheduler.mCurrentOperation.mClientMonitor);
         assertEquals(0, mScheduler.mPendingOperations.size());
 
         // Request it to be canceled. The operation can be canceled immediately, and the scheduler
         // should go back to idle, since in this case the framework has not even requested the HAL
         // to authenticate yet.
         mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */);
-        waitForIdle();
         assertTrue(client1.isAlreadyDone());
         assertTrue(client1.mDestroyed);
-        assertFalse(client1.mStartedHal);
         assertNull(mScheduler.mCurrentOperation);
     }
 
@@ -217,8 +210,8 @@
         // assertEquals(0, bsp.recentOperations.length);
 
         // Pretend the scheduler is busy enrolling, and check the proto dump again.
-        final TestHalClientMonitor client = new TestHalClientMonitor(mContext, mToken,
-                () -> mock(Object.class), 0, BiometricsProto.CM_ENROLL);
+        final TestClientMonitor2 client = new TestClientMonitor2(mContext, mToken,
+                () -> mock(Object.class), BiometricsProto.CM_ENROLL);
         mScheduler.scheduleClientMonitor(client);
         waitForIdle();
         bsp = getDump(true /* clearSchedulerBuffer */);
@@ -237,8 +230,8 @@
     @Test
     public void testProtoDump_fifo() throws Exception {
         // Add the first operation
-        final TestHalClientMonitor client = new TestHalClientMonitor(mContext, mToken,
-                () -> mock(Object.class), 0, BiometricsProto.CM_ENROLL);
+        final TestClientMonitor2 client = new TestClientMonitor2(mContext, mToken,
+                () -> mock(Object.class), BiometricsProto.CM_ENROLL);
         mScheduler.scheduleClientMonitor(client);
         waitForIdle();
         BiometricSchedulerProto bsp = getDump(false /* clearSchedulerBuffer */);
@@ -251,8 +244,8 @@
         client.getCallback().onClientFinished(client, true);
 
         // Add another operation
-        final TestHalClientMonitor client2 = new TestHalClientMonitor(mContext, mToken,
-                () -> mock(Object.class), 0, BiometricsProto.CM_REMOVE);
+        final TestClientMonitor2 client2 = new TestClientMonitor2(mContext, mToken,
+                () -> mock(Object.class), BiometricsProto.CM_REMOVE);
         mScheduler.scheduleClientMonitor(client2);
         waitForIdle();
         bsp = getDump(false /* clearSchedulerBuffer */);
@@ -263,8 +256,8 @@
         client2.getCallback().onClientFinished(client2, true);
 
         // And another operation
-        final TestHalClientMonitor client3 = new TestHalClientMonitor(mContext, mToken,
-                () -> mock(Object.class), 0, BiometricsProto.CM_AUTHENTICATE);
+        final TestClientMonitor2 client3 = new TestClientMonitor2(mContext, mToken,
+                () -> mock(Object.class), BiometricsProto.CM_AUTHENTICATE);
         mScheduler.scheduleClientMonitor(client3);
         waitForIdle();
         bsp = getDump(false /* clearSchedulerBuffer */);
@@ -297,7 +290,8 @@
     @Test
     public void testCancelPendingAuth() throws RemoteException {
         final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
-        final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, lazyDaemon);
+
+        final TestClientMonitor client1 = new TestClientMonitor(mContext, mToken, lazyDaemon);
         final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
         final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
                 mToken, callback);
@@ -308,12 +302,14 @@
         waitForIdle();
 
         assertEquals(mScheduler.getCurrentClient(), client1);
-        assertFalse(mScheduler.mPendingOperations.getFirst().isStarted());
+        assertEquals(Operation.STATE_WAITING_IN_QUEUE,
+                mScheduler.mPendingOperations.getFirst().mState);
 
         // Request cancel before the authentication client has started
         mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */);
         waitForIdle();
-        assertTrue(mScheduler.mPendingOperations.getFirst().isMarkedCanceling());
+        assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING,
+                mScheduler.mPendingOperations.getFirst().mState);
 
         // Finish the blocking client. The authentication client should send ERROR_CANCELED
         client1.getCallback().onClientFinished(client1, true /* success */);
@@ -330,109 +326,67 @@
 
     @Test
     public void testCancels_whenAuthRequestIdNotSet() {
-        testCancelsAuthDetectWhenRequestId(null /* requestId */, 2, true /* started */);
+        testCancelsWhenRequestId(null /* requestId */, 2, true /* started */);
     }
 
     @Test
     public void testCancels_whenAuthRequestIdNotSet_notStarted() {
-        testCancelsAuthDetectWhenRequestId(null /* requestId */, 2, false /* started */);
+        testCancelsWhenRequestId(null /* requestId */, 2, false /* started */);
     }
 
     @Test
     public void testCancels_whenAuthRequestIdMatches() {
-        testCancelsAuthDetectWhenRequestId(200L, 200, true /* started */);
+        testCancelsWhenRequestId(200L, 200, true /* started */);
     }
 
     @Test
     public void testCancels_whenAuthRequestIdMatches_noStarted() {
-        testCancelsAuthDetectWhenRequestId(200L, 200, false /* started */);
+        testCancelsWhenRequestId(200L, 200, false /* started */);
     }
 
     @Test
     public void testDoesNotCancel_whenAuthRequestIdMismatched() {
-        testCancelsAuthDetectWhenRequestId(10L, 20, true /* started */);
+        testCancelsWhenRequestId(10L, 20, true /* started */);
     }
 
     @Test
     public void testDoesNotCancel_whenAuthRequestIdMismatched_notStarted() {
-        testCancelsAuthDetectWhenRequestId(10L, 20, false /* started */);
-    }
-
-    private void testCancelsAuthDetectWhenRequestId(@Nullable Long requestId, long cancelRequestId,
-            boolean started) {
-        final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
-        final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
-        testCancelsWhenRequestId(requestId, cancelRequestId, started,
-                new TestAuthenticationClient(mContext, lazyDaemon, mToken, callback));
-    }
-
-    @Test
-    public void testCancels_whenEnrollRequestIdNotSet() {
-        testCancelsEnrollWhenRequestId(null /* requestId */, 2, false /* started */);
-    }
-
-    @Test
-    public void testCancels_whenEnrollRequestIdMatches() {
-        testCancelsEnrollWhenRequestId(200L, 200, false /* started */);
-    }
-
-    @Test
-    public void testDoesNotCancel_whenEnrollRequestIdMismatched() {
-        testCancelsEnrollWhenRequestId(10L, 20, false /* started */);
-    }
-
-    private void testCancelsEnrollWhenRequestId(@Nullable Long requestId, long cancelRequestId,
-            boolean started) {
-        final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
-        final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
-        testCancelsWhenRequestId(requestId, cancelRequestId, started,
-                new TestEnrollClient(mContext, lazyDaemon, mToken, callback));
+        testCancelsWhenRequestId(10L, 20, false /* started */);
     }
 
     private void testCancelsWhenRequestId(@Nullable Long requestId, long cancelRequestId,
-            boolean started, HalClientMonitor<?> client) {
+            boolean started) {
         final boolean matches = requestId == null || requestId == cancelRequestId;
+        final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
+        final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
+        final TestAuthenticationClient client = new TestAuthenticationClient(
+                mContext, lazyDaemon, mToken, callback);
         if (requestId != null) {
             client.setRequestId(requestId);
         }
 
-        final boolean isAuth = client instanceof TestAuthenticationClient;
-        final boolean isEnroll = client instanceof TestEnrollClient;
-
         mScheduler.scheduleClientMonitor(client);
         if (started) {
             mScheduler.startPreparedClient(client.getCookie());
         }
         waitForIdle();
-        if (isAuth) {
-            mScheduler.cancelAuthenticationOrDetection(mToken, cancelRequestId);
-        } else if (isEnroll) {
-            mScheduler.cancelEnrollment(mToken, cancelRequestId);
-        } else {
-            fail("unexpected operation type");
-        }
+        mScheduler.cancelAuthenticationOrDetection(mToken, cancelRequestId);
         waitForIdle();
 
-        if (isAuth) {
-            // auth clients that were waiting for cookie when canceled should never invoke the hal
-            final TestAuthenticationClient authClient = (TestAuthenticationClient) client;
-            assertEquals(matches && started ? 1 : 0, authClient.mNumCancels);
-            assertEquals(started, authClient.mStartedHal);
-        } else if (isEnroll) {
-            final TestEnrollClient enrollClient = (TestEnrollClient) client;
-            assertEquals(matches ? 1 : 0, enrollClient.mNumCancels);
-            assertTrue(enrollClient.mStartedHal);
-        }
+        assertEquals(matches && started ? 1 : 0, client.mNumCancels);
 
         if (matches) {
-            if (started || isEnroll) { // prep'd auth clients and enroll clients
-                assertTrue(mScheduler.mCurrentOperation.isCanceling());
+            if (started) {
+                assertEquals(Operation.STATE_STARTED_CANCELING,
+                        mScheduler.mCurrentOperation.mState);
             }
         } else {
-            if (started || isEnroll) { // prep'd auth clients and enroll clients
-                assertTrue(mScheduler.mCurrentOperation.isStarted());
+            if (started) {
+                assertEquals(Operation.STATE_STARTED,
+                        mScheduler.mCurrentOperation.mState);
             } else {
-                assertNotEquals(0, mScheduler.mCurrentOperation.isReadyToStart());
+                assertEquals(Operation.STATE_WAITING_FOR_COOKIE,
+                        mScheduler.mCurrentOperation.mState);
             }
         }
     }
@@ -457,14 +411,18 @@
         mScheduler.cancelAuthenticationOrDetection(mToken, 9999);
         waitForIdle();
 
-        assertTrue(mScheduler.mCurrentOperation.isStarted());
-        assertFalse(mScheduler.mPendingOperations.getFirst().isStarted());
+        assertEquals(Operation.STATE_STARTED,
+                mScheduler.mCurrentOperation.mState);
+        assertEquals(Operation.STATE_WAITING_IN_QUEUE,
+                mScheduler.mPendingOperations.getFirst().mState);
 
         mScheduler.cancelAuthenticationOrDetection(mToken, requestId2);
         waitForIdle();
 
-        assertTrue(mScheduler.mCurrentOperation.isStarted());
-        assertTrue(mScheduler.mPendingOperations.getFirst().isMarkedCanceling());
+        assertEquals(Operation.STATE_STARTED,
+                mScheduler.mCurrentOperation.mState);
+        assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING,
+                mScheduler.mPendingOperations.getFirst().mState);
     }
 
     @Test
@@ -501,12 +459,12 @@
     @Test
     public void testClientDestroyed_afterFinish() {
         final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class);
-        final TestHalClientMonitor client =
-                new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
+        final TestClientMonitor client =
+                new TestClientMonitor(mContext, mToken, nonNullDaemon);
         mScheduler.scheduleClientMonitor(client);
         client.mCallback.onClientFinished(client, true /* success */);
         waitForIdle();
-        assertTrue(client.mDestroyed);
+        assertTrue(client.wasDestroyed());
     }
 
     private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception {
@@ -514,10 +472,8 @@
     }
 
     private static class TestAuthenticationClient extends AuthenticationClient<Object> {
-        boolean mStartedHal = false;
-        boolean mStoppedHal = false;
-        boolean mDestroyed = false;
         int mNumCancels = 0;
+        boolean mDestroyed = false;
 
         public TestAuthenticationClient(@NonNull Context context,
                 @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
@@ -532,16 +488,18 @@
 
         @Override
         protected void stopHalOperation() {
-            mStoppedHal = true;
+
         }
 
         @Override
         protected void startHalOperation() {
-            mStartedHal = true;
+
         }
 
         @Override
-        protected void handleLifecycleAfterAuth(boolean authenticated) {}
+        protected void handleLifecycleAfterAuth(boolean authenticated) {
+
+        }
 
         @Override
         public boolean wasUserDetected() {
@@ -561,59 +519,36 @@
         }
     }
 
-    private static class TestEnrollClient extends EnrollClient<Object> {
-        boolean mStartedHal = false;
-        boolean mStoppedHal = false;
-        int mNumCancels = 0;
+    private static class TestClientMonitor2 extends TestClientMonitor {
+        private final int mProtoEnum;
 
-        TestEnrollClient(@NonNull Context context,
-                @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
-                @NonNull ClientMonitorCallbackConverter listener) {
-            super(context, lazyDaemon, token, listener, 0 /* userId */, new byte[69],
-                    "test" /* owner */, mock(BiometricUtils.class),
-                    5 /* timeoutSec */, 0 /* statsModality */, TEST_SENSOR_ID,
-                    true /* shouldVibrate */);
+        public TestClientMonitor2(@NonNull Context context, @NonNull IBinder token,
+                @NonNull LazyDaemon<Object> lazyDaemon, int protoEnum) {
+            super(context, token, lazyDaemon);
+            mProtoEnum = protoEnum;
         }
 
         @Override
-        protected void stopHalOperation() {
-            mStoppedHal = true;
-        }
-
-        @Override
-        protected void startHalOperation() {
-            mStartedHal = true;
-        }
-
-        @Override
-        protected boolean hasReachedEnrollmentLimit() {
-            return false;
-        }
-
-        @Override
-        public void cancel() {
-            mNumCancels++;
-            super.cancel();
+        public int getProtoEnum() {
+            return mProtoEnum;
         }
     }
 
-    private static class TestHalClientMonitor extends HalClientMonitor<Object> {
-        private final int mProtoEnum;
+    private static class TestClientMonitor extends HalClientMonitor<Object> {
         private boolean mUnableToStart;
         private boolean mStarted;
         private boolean mDestroyed;
 
-        TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token,
+        public TestClientMonitor(@NonNull Context context, @NonNull IBinder token,
                 @NonNull LazyDaemon<Object> lazyDaemon) {
-            this(context, token, lazyDaemon, 0 /* cookie */, BiometricsProto.CM_UPDATE_ACTIVE_USER);
+            this(context, token, lazyDaemon, 0 /* cookie */);
         }
 
-        TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token,
-                @NonNull LazyDaemon<Object> lazyDaemon, int cookie, int protoEnum) {
+        public TestClientMonitor(@NonNull Context context, @NonNull IBinder token,
+                @NonNull LazyDaemon<Object> lazyDaemon, int cookie) {
             super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */,
                     TAG, cookie, TEST_SENSOR_ID, 0 /* statsModality */,
                     0 /* statsAction */, 0 /* statsClient */);
-            mProtoEnum = protoEnum;
         }
 
         @Override
@@ -624,7 +559,9 @@
 
         @Override
         public int getProtoEnum() {
-            return mProtoEnum;
+            // Anything other than CM_NONE, which is used to represent "idle". Tests that need
+            // real proto enums should use TestClientMonitor2
+            return BiometricsProto.CM_UPDATE_ACTIVE_USER;
         }
 
         @Override
@@ -636,7 +573,7 @@
 
         @Override
         protected void startHalOperation() {
-            mStarted = true;
+
         }
 
         @Override
@@ -644,9 +581,22 @@
             super.destroy();
             mDestroyed = true;
         }
+
+        public boolean wasUnableToStart() {
+            return mUnableToStart;
+        }
+
+        public boolean hasStarted() {
+            return mStarted;
+        }
+
+        public boolean wasDestroyed() {
+            return mDestroyed;
+        }
+
     }
 
-    private void waitForIdle() {
-        TestableLooper.get(this).processAllMessages();
+    private static void waitForIdle() {
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/LockoutResetDispatcherTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/LockoutResetDispatcherTest.java
new file mode 100644
index 0000000..a53e22e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/LockoutResetDispatcherTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class LockoutResetDispatcherTest {
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+    @Mock
+    private IBinder mBinder;
+    @Mock
+    private IBiometricServiceLockoutResetCallback mCallback;
+
+    private LockoutResetDispatcher mDispatcher;
+
+    @Before
+    public void setup() {
+        when(mCallback.asBinder()).thenReturn(mBinder);
+        mDispatcher = new LockoutResetDispatcher(mContext);
+    }
+
+    @Test
+    public void linksToDeath() throws Exception {
+        mDispatcher.addCallback(mCallback, "package");
+        verify(mBinder).linkToDeath(eq(mDispatcher), anyInt());
+    }
+
+    @Test
+    public void notifyLockoutReset() throws Exception {
+        final int sensorId = 24;
+
+        mDispatcher.addCallback(mCallback, "some.package");
+        mDispatcher.notifyLockoutResetCallbacks(sensorId);
+
+        final ArgumentCaptor<IRemoteCallback> captor =
+                ArgumentCaptor.forClass(IRemoteCallback.class);
+        verify(mCallback).onLockoutReset(eq(sensorId), captor.capture());
+        captor.getValue().sendResult(new Bundle());
+    }
+
+    @Test
+    public void releaseWakeLockOnDeath() {
+        mDispatcher.addCallback(mCallback, "a.b.cee");
+        mDispatcher.binderDied(mBinder);
+
+        // would be better to check the wake lock
+        // but this project lacks the extended mockito support to do it
+        assertThat(mDispatcher.mClientCallbacks).isEmpty();
+    }
+
+    @Test
+    public void releaseCorrectWakeLockOnDeath() {
+        mDispatcher.addCallback(mCallback, "a.b");
+        mDispatcher.binderDied(mock(IBinder.class));
+
+        assertThat(mDispatcher.mClientCallbacks).hasSize(1);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
index 407f5fb..7fccd49 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.server.biometrics.sensors;
 
-import static android.testing.TestableLooper.RunWithLooper;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
@@ -30,53 +28,52 @@
 import android.content.Context;
 import android.hardware.biometrics.IBiometricService;
 import android.os.Binder;
-import android.os.Handler;
 import android.os.IBinder;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
 import org.junit.Test;
-import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 @Presubmit
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
 @SmallTest
 public class UserAwareBiometricSchedulerTest {
 
-    private static final String TAG = "UserAwareBiometricSchedulerTest";
+    private static final String TAG = "BiometricSchedulerTest";
     private static final int TEST_SENSOR_ID = 0;
 
-    private Handler mHandler;
     private UserAwareBiometricScheduler mScheduler;
-    private IBinder mToken = new Binder();
+    private IBinder mToken;
 
     @Mock
     private Context mContext;
     @Mock
     private IBiometricService mBiometricService;
 
-    private TestUserStartedCallback mUserStartedCallback = new TestUserStartedCallback();
-    private TestUserStoppedCallback mUserStoppedCallback = new TestUserStoppedCallback();
+    private TestUserStartedCallback mUserStartedCallback;
+    private TestUserStoppedCallback mUserStoppedCallback;
     private int mCurrentUserId = UserHandle.USER_NULL;
-    private boolean mStartOperationsFinish = true;
-    private int mStartUserClientCount = 0;
+    private boolean mStartOperationsFinish;
+    private int mStartUserClientCount;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mHandler = new Handler(TestableLooper.get(this).getLooper());
+
+        mToken = new Binder();
+        mStartOperationsFinish = true;
+        mStartUserClientCount = 0;
+        mUserStartedCallback = new TestUserStartedCallback();
+        mUserStoppedCallback = new TestUserStoppedCallback();
+
         mScheduler = new UserAwareBiometricScheduler(TAG,
-                mHandler,
                 BiometricScheduler.SENSOR_TYPE_UNKNOWN,
                 null /* gestureAvailabilityDispatcher */,
                 mBiometricService,
@@ -120,7 +117,7 @@
         mCurrentUserId = UserHandle.USER_NULL;
         mStartOperationsFinish = false;
 
-        final BaseClientMonitor[] nextClients = new BaseClientMonitor[]{
+        final BaseClientMonitor[] nextClients = new BaseClientMonitor[] {
                 mock(BaseClientMonitor.class),
                 mock(BaseClientMonitor.class),
                 mock(BaseClientMonitor.class)
@@ -150,11 +147,11 @@
         waitForIdle();
 
         final TestStartUserClient startUserClient =
-                (TestStartUserClient) mScheduler.mCurrentOperation.getClientMonitor();
+                (TestStartUserClient) mScheduler.mCurrentOperation.mClientMonitor;
         mScheduler.reset();
         assertNull(mScheduler.mCurrentOperation);
 
-        final BiometricSchedulerOperation fakeOperation = new BiometricSchedulerOperation(
+        final BiometricScheduler.Operation fakeOperation = new BiometricScheduler.Operation(
                 mock(BaseClientMonitor.class), new BaseClientMonitor.Callback() {});
         mScheduler.mCurrentOperation = fakeOperation;
         startUserClient.mCallback.onClientFinished(startUserClient, true);
@@ -197,8 +194,8 @@
         verify(nextClient).start(any());
     }
 
-    private void waitForIdle() {
-        TestableLooper.get(this).processAllMessages();
+    private static void waitForIdle() {
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
 
     private class TestUserStoppedCallback implements StopUserClient.UserStoppedCallback {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index 0891eca..a13dff2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -79,7 +79,6 @@
         when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
 
         mScheduler = new UserAwareBiometricScheduler(TAG,
-                new Handler(mLooper.getLooper()),
                 BiometricScheduler.SENSOR_TYPE_FACE,
                 null /* gestureAvailabilityDispatcher */,
                 () -> USER_ID,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
index 21a7a8a..39c51d5 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
@@ -32,9 +32,7 @@
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.Binder;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 
@@ -71,7 +69,6 @@
     @Mock
     private BiometricScheduler mScheduler;
 
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
     private LockoutResetDispatcher mLockoutResetDispatcher;
     private com.android.server.biometrics.sensors.face.hidl.Face10 mFace10;
     private IBinder mBinder;
@@ -100,7 +97,7 @@
                 resetLockoutRequiresChallenge);
 
         Face10.sSystemClock = Clock.fixed(Instant.ofEpochMilli(100), ZoneId.of("PST"));
-        mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mHandler, mScheduler);
+        mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mScheduler);
         mBinder = new Binder();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index a012b8b..0d520ca 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -79,7 +79,6 @@
         when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
 
         mScheduler = new UserAwareBiometricScheduler(TAG,
-                new Handler(mLooper.getLooper()),
                 BiometricScheduler.SENSOR_TYPE_FP_OTHER,
                 null /* gestureAvailabilityDispatcher */,
                 () -> USER_ID,
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index c7c0756..d79a833 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -26,6 +26,7 @@
 import static org.testng.Assert.assertThrows;
 
 import android.Manifest;
+import android.companion.virtual.VirtualDeviceParams;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.graphics.Point;
@@ -84,7 +85,7 @@
         mInputController = new InputController(new Object(), mNativeWrapperMock);
         mDeviceImpl = new VirtualDeviceImpl(mContext,
                 /* association info */ null, new Binder(), /* uid */ 0, mInputController,
-                (int associationId) -> {});
+                (int associationId) -> {}, new VirtualDeviceParams.Builder().build());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
new file mode 100644
index 0000000..77f1e24
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.companion.virtual;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.companion.virtual.VirtualDeviceParams;
+import android.os.Parcel;
+import android.os.UserHandle;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualDeviceParamsTest {
+
+    @Test
+    public void parcelable_shouldRecreateSuccessfully() {
+        VirtualDeviceParams originalParams = new VirtualDeviceParams.Builder()
+                .setLockState(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED)
+                .setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456)))
+                .build();
+        Parcel parcel = Parcel.obtain();
+        originalParams.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        VirtualDeviceParams params = VirtualDeviceParams.CREATOR.createFromParcel(parcel);
+        assertThat(params).isEqualTo(originalParams);
+        assertThat(params.getLockState()).isEqualTo(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED);
+        assertThat(params.getUsersWithMatchingAccounts())
+                .containsExactly(UserHandle.of(123), UserHandle.of(456));
+    }
+}
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 3c809f9..1228d62 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -37,6 +37,8 @@
 import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
 import static android.app.admin.PasswordMetrics.computeForPasswordOrPin;
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
 import static android.net.InetAddresses.parseNumericAddress;
 
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
@@ -100,7 +102,7 @@
 import android.content.pm.UserInfo;
 import android.graphics.Color;
 import android.hardware.usb.UsbManager;
-import android.net.ConnectivityManager;
+import android.net.ProfileNetworkPreference;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
@@ -4058,12 +4060,15 @@
         mServiceContext.permissions.add(permission.INTERACT_ACROSS_USERS_FULL);
 
         dpms.handleStartUser(managedProfileUserId);
-        verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference(
-                eq(UserHandle.of(managedProfileUserId)),
-                anyInt(),
-                any(),
-                any()
-        );
+        ProfileNetworkPreference preferenceDetails =
+                new ProfileNetworkPreference.Builder()
+                .setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT)
+                .build();
+        List<ProfileNetworkPreference> preferences = new ArrayList<>();
+        preferences.add(preferenceDetails);
+        verify(getServices().connectivityManager, times(1))
+                .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences,
+                null, null);
     }
 
     @Test
@@ -4075,12 +4080,15 @@
         mServiceContext.permissions.add(permission.INTERACT_ACROSS_USERS_FULL);
 
         dpms.handleStopUser(managedProfileUserId);
-        verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference(
-                eq(UserHandle.of(managedProfileUserId)),
-                eq(ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT),
-                any(),
-                any()
-        );
+        ProfileNetworkPreference preferenceDetails =
+                new ProfileNetworkPreference.Builder()
+                        .setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT)
+                        .build();
+        List<ProfileNetworkPreference> preferences = new ArrayList<>();
+        preferences.add(preferenceDetails);
+        verify(getServices().connectivityManager, times(1))
+                .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences,
+                        null, null);
     }
 
     @Test
@@ -4098,21 +4106,29 @@
 
         dpm.setPreferentialNetworkServiceEnabled(false);
         assertThat(dpm.isPreferentialNetworkServiceEnabled()).isFalse();
-        verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference(
-                eq(UserHandle.of(managedProfileUserId)),
-                eq(ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT),
-                any(),
-                any()
-        );
+
+        ProfileNetworkPreference preferenceDetails =
+                new ProfileNetworkPreference.Builder()
+                        .setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT)
+                        .build();
+        List<ProfileNetworkPreference> preferences = new ArrayList<>();
+        preferences.add(preferenceDetails);
+        verify(getServices().connectivityManager, times(1))
+                .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences,
+                        null, null);
 
         dpm.setPreferentialNetworkServiceEnabled(true);
         assertThat(dpm.isPreferentialNetworkServiceEnabled()).isTrue();
-        verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference(
-                eq(UserHandle.of(managedProfileUserId)),
-                eq(ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE),
-                any(),
-                any()
-        );
+
+        ProfileNetworkPreference preferenceDetails2 =
+                new ProfileNetworkPreference.Builder()
+                        .setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE)
+                        .build();
+        List<ProfileNetworkPreference> preferences2 = new ArrayList<>();
+        preferences2.add(preferenceDetails);
+        verify(getServices().connectivityManager, times(1))
+                .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences2,
+                        null, null);
     }
 
     @Test
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 70e78eb..c771000 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -17,9 +17,7 @@
 package com.android.server.locales;
 
 import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
@@ -43,11 +41,10 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
-import android.os.Environment;
 import android.os.LocaleList;
 import android.os.RemoteException;
 import android.os.SimpleClock;
-import android.util.AtomicFile;
+import android.util.SparseArray;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
 import android.util.Xml;
@@ -57,6 +54,7 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.util.XmlUtils;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -65,10 +63,7 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
 import java.time.Clock;
@@ -95,9 +90,8 @@
             LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS);
     private static final Map<String, String> DEFAULT_PACKAGE_LOCALES_MAP = Map.of(
             DEFAULT_PACKAGE_NAME, DEFAULT_LOCALE_TAGS);
-    private static final File STAGED_LOCALES_DIR = new File(
-            Environment.getExternalStorageDirectory(), "lmsUnitTests");
-
+    private static final SparseArray<LocaleManagerBackupHelper.StagedData> STAGE_DATA =
+            new SparseArray<>();
 
     private LocaleManagerBackupHelper mBackupHelper;
     private long mCurrentTimeMillis;
@@ -138,14 +132,17 @@
         doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
 
         mBackupHelper = spy(new ShadowLocaleManagerBackupHelper(mMockContext,
-                mMockLocaleManagerService, mMockPackageManagerInternal,
-                new File(Environment.getExternalStorageDirectory(), "lmsUnitTests"), mClock));
+                mMockLocaleManagerService, mMockPackageManagerInternal, mClock, STAGE_DATA));
         doNothing().when(mBackupHelper).notifyBackupManager();
 
         mUserMonitor = mBackupHelper.getUserMonitor();
         mPackageMonitor = mBackupHelper.getPackageMonitor();
         setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS);
-        cleanStagedFiles();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        STAGE_DATA.clear();
     }
 
     @Test
@@ -203,25 +200,25 @@
     }
 
     @Test
-    public void testRestore_nullPayload_nothingRestoredAndNoStageFile() throws Exception {
+    public void testRestore_nullPayload_nothingRestoredAndNoStageData() throws Exception {
         mBackupHelper.stageAndApplyRestoredPayload(/* payload= */ null, DEFAULT_USER_ID);
 
         verifyNothingRestored();
-        checkStageFileDoesNotExist(DEFAULT_USER_ID);
+        checkStageDataDoesNotExist(DEFAULT_USER_ID);
     }
 
     @Test
-    public void testRestore_zeroLengthPayload_nothingRestoredAndNoStageFile() throws Exception {
+    public void testRestore_zeroLengthPayload_nothingRestoredAndNoStageData() throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
         mBackupHelper.stageAndApplyRestoredPayload(/* payload= */ out.toByteArray(),
                 DEFAULT_USER_ID);
 
         verifyNothingRestored();
-        checkStageFileDoesNotExist(DEFAULT_USER_ID);
+        checkStageDataDoesNotExist(DEFAULT_USER_ID);
     }
 
     @Test
-    public void testRestore_allAppsInstalled_noStageFileCreated() throws Exception {
+    public void testRestore_allAppsInstalled_noStageDataCreated() throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
         writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
 
@@ -234,8 +231,7 @@
         verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
                 DEFAULT_USER_ID, DEFAULT_LOCALES);
 
-        // Stage file wasn't created.
-        checkStageFileDoesNotExist(DEFAULT_USER_ID);
+        checkStageDataDoesNotExist(DEFAULT_USER_ID);
     }
 
     @Test
@@ -248,8 +244,8 @@
         mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
 
         verifyNothingRestored();
-        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP,
-                getStageFileIfExists(DEFAULT_USER_ID), DEFAULT_CREATION_TIME_MILLIS);
+        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP,
+                DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
     }
 
     @Test
@@ -257,8 +253,10 @@
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
         HashMap<String, String> pkgLocalesMap = new HashMap<>();
 
-        String pkgNameA = "com.android.myAppA", pkgNameB = "com.android.myAppB";
-        String langTagsA = "ru", langTagsB = "hi,fr";
+        String pkgNameA = "com.android.myAppA";
+        String pkgNameB = "com.android.myAppB";
+        String langTagsA = "ru";
+        String langTagsB = "hi,fr";
         pkgLocalesMap.put(pkgNameA, langTagsA);
         pkgLocalesMap.put(pkgNameB, langTagsB);
         writeTestPayload(out, pkgLocalesMap);
@@ -273,12 +271,12 @@
                 LocaleList.forLanguageTags(langTagsA));
 
         pkgLocalesMap.remove(pkgNameA);
-        verifyStageFileContent(pkgLocalesMap, getStageFileIfExists(DEFAULT_USER_ID),
-                DEFAULT_CREATION_TIME_MILLIS);
+        verifyStageDataForUser(pkgLocalesMap,
+                DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
     }
 
     @Test
-    public void testRestore_appLocalesAlreadySet_nothingRestoredAndNoStageFile() throws Exception {
+    public void testRestore_appLocalesAlreadySet_nothingRestoredAndNoStageData() throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
         writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
 
@@ -289,8 +287,7 @@
 
         // Since locales are already set, we should not restore anything for it.
         verifyNothingRestored();
-        // Stage file wasn't created
-        checkStageFileDoesNotExist(DEFAULT_USER_ID);
+        checkStageDataDoesNotExist(DEFAULT_USER_ID);
     }
 
     @Test
@@ -299,9 +296,12 @@
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
         HashMap<String, String> pkgLocalesMap = new HashMap<>();
 
-        String pkgNameA = "com.android.myAppA", pkgNameB = "com.android.myAppB", pkgNameC =
-                "com.android.myAppC";
-        String langTagsA = "ru", langTagsB = "hi,fr", langTagsC = "zh,es";
+        String pkgNameA = "com.android.myAppA";
+        String pkgNameB = "com.android.myAppB";
+        String pkgNameC = "com.android.myAppC";
+        String langTagsA = "ru";
+        String langTagsB = "hi,fr";
+        String langTagsC = "zh,es";
         pkgLocalesMap.put(pkgNameA, langTagsA);
         pkgLocalesMap.put(pkgNameB, langTagsB);
         pkgLocalesMap.put(pkgNameC, langTagsC);
@@ -328,8 +328,8 @@
         // App C is staged.
         pkgLocalesMap.remove(pkgNameA);
         pkgLocalesMap.remove(pkgNameB);
-        verifyStageFileContent(pkgLocalesMap, getStageFileIfExists(DEFAULT_USER_ID),
-                DEFAULT_CREATION_TIME_MILLIS);
+        verifyStageDataForUser(pkgLocalesMap,
+                DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
     }
 
     @Test
@@ -341,15 +341,15 @@
 
         mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
 
-        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, getStageFileIfExists(DEFAULT_USER_ID),
-                DEFAULT_CREATION_TIME_MILLIS);
+        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP,
+                DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
 
         final long newCreationTime = DEFAULT_CREATION_TIME_MILLIS + 100;
         setCurrentTimeMillis(newCreationTime);
         mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
 
-        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, getStageFileIfExists(DEFAULT_USER_ID),
-                newCreationTime);
+        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP,
+                newCreationTime, DEFAULT_USER_ID);
     }
 
     @Test
@@ -357,8 +357,10 @@
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
         HashMap<String, String> pkgLocalesMap = new HashMap<>();
 
-        String pkgNameA = "com.android.myAppA", pkgNameB = "com.android.myAppB";
-        String langTagsA = "ru", langTagsB = "hi,fr";
+        String pkgNameA = "com.android.myAppA";
+        String pkgNameB = "com.android.myAppB";
+        String langTagsA = "ru";
+        String langTagsB = "hi,fr";
         pkgLocalesMap.put(pkgNameA, langTagsA);
         pkgLocalesMap.put(pkgNameB, langTagsB);
         writeTestPayload(out, pkgLocalesMap);
@@ -380,8 +382,7 @@
                 LocaleList.forLanguageTags(langTagsA));
 
         pkgLocalesMap.remove(pkgNameA);
-        verifyStageFileContent(pkgLocalesMap, getStageFileIfExists(DEFAULT_USER_ID),
-                DEFAULT_CREATION_TIME_MILLIS);
+        verifyStageDataForUser(pkgLocalesMap, DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
 
         setUpPackageInstalled(pkgNameB);
 
@@ -389,7 +390,7 @@
 
         verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
                 LocaleList.forLanguageTags(langTagsB));
-        checkStageFileDoesNotExist(DEFAULT_USER_ID);
+        checkStageDataDoesNotExist(DEFAULT_USER_ID);
     }
 
     @Test
@@ -404,8 +405,8 @@
         mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
 
         verifyNothingRestored();
-        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, getStageFileIfExists(DEFAULT_USER_ID),
-                DEFAULT_CREATION_TIME_MILLIS);
+        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP,
+                DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
 
         // App is installed later (post SUW).
         setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
@@ -415,11 +416,11 @@
 
         // Since locales are already set, we should not restore anything for it.
         verifyNothingRestored();
-        checkStageFileDoesNotExist(DEFAULT_USER_ID);
+        checkStageDataDoesNotExist(DEFAULT_USER_ID);
     }
 
     @Test
-    public void testStageFileDeletion_backupPassRunAfterRetentionPeriod_stageFileDeleted()
+    public void testStageDataDeletion_backupPassRunAfterRetentionPeriod_stageDataDeleted()
             throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
         writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
@@ -429,8 +430,8 @@
         mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
 
         verifyNothingRestored();
-        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, getStageFileIfExists(DEFAULT_USER_ID),
-                DEFAULT_CREATION_TIME_MILLIS);
+        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP,
+                DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
 
         // Retention period has not elapsed.
         setCurrentTimeMillis(
@@ -439,32 +440,78 @@
                 .getInstalledApplications(anyLong(), anyInt(), anyInt());
         assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
 
-        // Stage file should NOT be deleted.
-        checkStageFileExists(DEFAULT_USER_ID);
+        checkStageDataExists(DEFAULT_USER_ID);
 
-        // Exactly RETENTION_PERIOD amount of time has passed so stage file should still not be
+        // Exactly RETENTION_PERIOD amount of time has passed so stage data should still not be
         // removed.
         setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.toMillis());
         doReturn(List.of()).when(mMockPackageManagerInternal)
                 .getInstalledApplications(anyLong(), anyInt(), anyInt());
         assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
 
-        // Stage file should NOT be deleted.
-        checkStageFileExists(DEFAULT_USER_ID);
+        checkStageDataExists(DEFAULT_USER_ID);
 
-        // Retention period has now expired, stage file should be deleted.
+        // Retention period has now expired, stage data should be deleted.
         setCurrentTimeMillis(
                 DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.plusSeconds(1).toMillis());
         doReturn(List.of()).when(mMockPackageManagerInternal)
                 .getInstalledApplications(anyLong(), anyInt(), anyInt());
         assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
 
-        // Stage file should be deleted.
-        checkStageFileDoesNotExist(DEFAULT_USER_ID);
+        checkStageDataDoesNotExist(DEFAULT_USER_ID);
     }
 
     @Test
-    public void testUserRemoval_userRemoved_stageFileDeleted() throws Exception {
+    public void testStageDataDeletion_lazyRestoreAfterRetentionPeriod_stageDataDeleted()
+            throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        HashMap<String, String> pkgLocalesMap = new HashMap<>();
+
+        String pkgNameA = "com.android.myAppA";
+        String pkgNameB = "com.android.myAppB";
+        String langTagsA = "ru";
+        String langTagsB = "hi,fr";
+        pkgLocalesMap.put(pkgNameA, langTagsA);
+        pkgLocalesMap.put(pkgNameB, langTagsB);
+        writeTestPayload(out, pkgLocalesMap);
+
+        setUpPackageNotInstalled(pkgNameA);
+        setUpPackageNotInstalled(pkgNameB);
+        setUpLocalesForPackage(pkgNameA, LocaleList.getEmptyLocaleList());
+        setUpLocalesForPackage(pkgNameB, LocaleList.getEmptyLocaleList());
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        verifyNothingRestored();
+        verifyStageDataForUser(pkgLocalesMap, DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
+
+        // Retention period has not elapsed.
+        setCurrentTimeMillis(
+                DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.minusHours(1).toMillis());
+
+        setUpPackageInstalled(pkgNameA);
+        mPackageMonitor.onPackageAdded(pkgNameA, DEFAULT_UID);
+
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
+                LocaleList.forLanguageTags(langTagsA));
+
+        pkgLocalesMap.remove(pkgNameA);
+        verifyStageDataForUser(pkgLocalesMap, DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
+
+        // Retention period has now expired, stage data should be deleted.
+        setCurrentTimeMillis(
+                DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.plusSeconds(1).toMillis());
+        setUpPackageInstalled(pkgNameB);
+        mPackageMonitor.onPackageAdded(pkgNameB, DEFAULT_UID);
+
+        verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameB), anyInt(),
+                any());
+
+        checkStageDataDoesNotExist(DEFAULT_USER_ID);
+    }
+
+    @Test
+    public void testUserRemoval_userRemoved_stageDataDeleted() throws Exception {
         final ByteArrayOutputStream outDefault = new ByteArrayOutputStream();
         writeTestPayload(outDefault, DEFAULT_PACKAGE_LOCALES_MAP);
 
@@ -485,119 +532,20 @@
 
         verifyNothingRestored();
 
-        // Verify stage file contents.
-        AtomicFile stageFileDefaultUser = getStageFileIfExists(DEFAULT_USER_ID);
-        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, stageFileDefaultUser,
-                DEFAULT_CREATION_TIME_MILLIS);
-
-        AtomicFile stageFileWorkProfile = getStageFileIfExists(WORK_PROFILE_USER_ID);
-        verifyStageFileContent(pkgLocalesMapWorkProfile, stageFileWorkProfile,
-                DEFAULT_CREATION_TIME_MILLIS);
+        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP,
+                DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
+        verifyStageDataForUser(pkgLocalesMapWorkProfile,
+                DEFAULT_CREATION_TIME_MILLIS, WORK_PROFILE_USER_ID);
 
         Intent intent = new Intent();
         intent.setAction(Intent.ACTION_USER_REMOVED);
         intent.putExtra(Intent.EXTRA_USER_HANDLE, DEFAULT_USER_ID);
         mUserMonitor.onReceive(mMockContext, intent);
 
-        // Stage file should be removed only for DEFAULT_USER_ID.
-        checkStageFileDoesNotExist(DEFAULT_USER_ID);
-        verifyStageFileContent(pkgLocalesMapWorkProfile, stageFileWorkProfile,
-                DEFAULT_CREATION_TIME_MILLIS);
-    }
-
-    @Test
-    public void testLoadStageFiles_invalidNameFormat_stageFileDeleted() throws Exception {
-        // Stage file name should be : staged_locales_<user_id_int>.xml
-        File stageFile = new File(STAGED_LOCALES_DIR, "xyz.xml");
-        assertTrue(stageFile.createNewFile());
-        assertTrue(stageFile.isFile());
-
-        // Putting valid xml data in file.
-        FileOutputStream out = new FileOutputStream(stageFile);
-        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP, /* forStage= */
-                true, /* creationTimeMillis= */ 0);
-        out.flush();
-        out.close();
-
-        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP,
-                new AtomicFile(stageFile), /* creationTimeMillis= */ 0);
-
-        mBackupHelper = new LocaleManagerBackupHelper(mMockContext, mMockLocaleManagerService,
-                mMockPackageManagerInternal, STAGED_LOCALES_DIR, mClock);
-        assertFalse(stageFile.isFile());
-    }
-
-    @Test
-    public void testLoadStageFiles_userIdNotParseable_stageFileDeleted() throws Exception {
-        // Stage file name should be : staged_locales_<user_id_int>.xml
-        File stageFile = new File(STAGED_LOCALES_DIR, "staged_locales_abc.xml");
-        assertTrue(stageFile.createNewFile());
-        assertTrue(stageFile.isFile());
-
-        // Putting valid xml data in file.
-        FileOutputStream out = new FileOutputStream(stageFile);
-        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP, /* forStage= */
-                true, /* creationTimeMillis= */ 0);
-        out.flush();
-        out.close();
-
-        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP,
-                new AtomicFile(stageFile), /* creationTimeMillis= */ 0);
-
-        mBackupHelper = new LocaleManagerBackupHelper(mMockContext, mMockLocaleManagerService,
-                mMockPackageManagerInternal, STAGED_LOCALES_DIR, mClock);
-        assertFalse(stageFile.isFile());
-    }
-
-    @Test
-    public void testLoadStageFiles_invalidContent_stageFileDeleted() throws Exception {
-        File stageFile = new File(STAGED_LOCALES_DIR, "staged_locales_0.xml");
-        assertTrue(stageFile.createNewFile());
-        assertTrue(stageFile.isFile());
-
-        FileOutputStream out = new FileOutputStream(stageFile);
-        out.write("some_non_xml_string".getBytes());
-        out.close();
-
-        mBackupHelper = new LocaleManagerBackupHelper(mMockContext, mMockLocaleManagerService,
-                mMockPackageManagerInternal, STAGED_LOCALES_DIR, mClock);
-        assertFalse(stageFile.isFile());
-    }
-
-    @Test
-    public void testLoadStageFiles_validContent_doesLazyRestore() throws Exception {
-        File stageFile = new File(STAGED_LOCALES_DIR, "staged_locales_0.xml");
-        assertTrue(stageFile.createNewFile());
-        assertTrue(stageFile.isFile());
-
-        // Putting valid xml data in file.
-        FileOutputStream out = new FileOutputStream(stageFile);
-        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP, /* forStage= */
-                true, DEFAULT_CREATION_TIME_MILLIS);
-        out.flush();
-        out.close();
-
-        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP,
-                new AtomicFile(stageFile), DEFAULT_CREATION_TIME_MILLIS);
-
-        mBackupHelper = new LocaleManagerBackupHelper(mMockContext, mMockLocaleManagerService,
-                mMockPackageManagerInternal, STAGED_LOCALES_DIR, mClock);
-        mPackageMonitor = mBackupHelper.getPackageMonitor();
-
-        // Stage file still exists.
-        assertTrue(stageFile.isFile());
-
-        // App is installed later.
-        setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
-        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList());
-
-        mPackageMonitor.onPackageAdded(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
-
-        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
-                DEFAULT_USER_ID, DEFAULT_LOCALES);
-
-        // Stage file gets deleted here because all staged locales have been applied.
-        assertFalse(stageFile.isFile());
+        // Stage data should be removed only for DEFAULT_USER_ID.
+        checkStageDataDoesNotExist(DEFAULT_USER_ID);
+        verifyStageDataForUser(pkgLocalesMapWorkProfile,
+                DEFAULT_CREATION_TIME_MILLIS, WORK_PROFILE_USER_ID);
     }
 
     private void setUpPackageInstalled(String packageName) throws Exception {
@@ -633,27 +581,15 @@
                 any());
     }
 
-
     private static void verifyPayloadForAppLocales(Map<String, String> expectedPkgLocalesMap,
             byte[] payload)
             throws IOException, XmlPullParserException {
-        verifyPayloadForAppLocales(expectedPkgLocalesMap, payload, /* forStage= */ false, -1);
-    }
-
-    private static void verifyPayloadForAppLocales(Map<String, String> expectedPkgLocalesMap,
-            byte[] payload, boolean forStage, long expectedCreationTime)
-            throws IOException, XmlPullParserException {
         final ByteArrayInputStream stream = new ByteArrayInputStream(payload);
         final TypedXmlPullParser parser = Xml.newFastPullParser();
         parser.setInput(stream, StandardCharsets.UTF_8.name());
 
         Map<String, String> backupDataMap = new HashMap<>();
         XmlUtils.beginDocument(parser, TEST_LOCALES_XML_TAG);
-        if (forStage) {
-            long actualCreationTime = parser.getAttributeLong(/* namespace= */ null,
-                    "creationTimeMillis");
-            assertEquals(expectedCreationTime, actualCreationTime);
-        }
         int depth = parser.getDepth();
         while (XmlUtils.nextElementWithin(parser, depth)) {
             if (parser.getName().equals("package")) {
@@ -668,13 +604,6 @@
 
     private static void writeTestPayload(OutputStream stream, Map<String, String> pkgLocalesMap)
             throws IOException {
-        writeTestPayload(stream, pkgLocalesMap, /* forStage= */ false, /* creationTimeMillis= */
-                -1);
-    }
-
-    private static void writeTestPayload(OutputStream stream, Map<String, String> pkgLocalesMap,
-            boolean forStage, long creationTimeMillis)
-            throws IOException {
         if (pkgLocalesMap.isEmpty()) {
             return;
         }
@@ -684,11 +613,6 @@
         out.startDocument(/* encoding= */ null, /* standalone= */ true);
         out.startTag(/* namespace= */ null, TEST_LOCALES_XML_TAG);
 
-        if (forStage) {
-            out.attribute(/* namespace= */ null, "creationTimeMillis",
-                    Long.toString(creationTimeMillis));
-        }
-
         for (String pkg : pkgLocalesMap.keySet()) {
             out.startTag(/* namespace= */ null, "package");
             out.attribute(/* namespace= */ null, "name", pkg);
@@ -700,41 +624,19 @@
         out.endDocument();
     }
 
-    private static void verifyStageFileContent(Map<String, String> expectedPkgLocalesMap,
-            AtomicFile stageFile,
-            long creationTimeMillis)
-            throws Exception {
-        assertNotNull(stageFile);
-        try (InputStream stagedDataInputStream = stageFile.openRead()) {
-            verifyPayloadForAppLocales(expectedPkgLocalesMap, stagedDataInputStream.readAllBytes(),
-                    /* forStage= */ true, creationTimeMillis);
-        } catch (IOException | XmlPullParserException e) {
-            throw e;
-        }
+    private void verifyStageDataForUser(Map<String, String> expectedPkgLocalesMap,
+            long expectedCreationTimeMillis, int userId) {
+        LocaleManagerBackupHelper.StagedData stagedDataForUser = STAGE_DATA.get(userId);
+        assertNotNull(stagedDataForUser);
+        assertEquals(expectedCreationTimeMillis, stagedDataForUser.mCreationTimeMillis);
+        assertEquals(expectedPkgLocalesMap, stagedDataForUser.mPackageStates);
     }
 
-    private static void checkStageFileDoesNotExist(int userId) {
-        assertNull(getStageFileIfExists(userId));
+    private static void checkStageDataExists(int userId) {
+        assertNotNull(STAGE_DATA.get(userId));
     }
 
-    private static void checkStageFileExists(int userId) {
-        assertNotNull(getStageFileIfExists(userId));
-    }
-
-    private static AtomicFile getStageFileIfExists(int userId) {
-        File file = new File(STAGED_LOCALES_DIR, String.format("staged_locales_%d.xml", userId));
-        if (file.isFile()) {
-            return new AtomicFile(file);
-        }
-        return null;
-    }
-
-    private static void cleanStagedFiles() {
-        File[] files = STAGED_LOCALES_DIR.listFiles();
-        if (files != null) {
-            for (File f : files) {
-                f.delete();
-            }
-        }
+    private static void checkStageDataDoesNotExist(int userId) {
+        assertNull(STAGE_DATA.get(userId));
     }
 }
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 93972c3..b0fc636 100644
--- a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
+++ b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
@@ -18,8 +18,8 @@
 
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
+import android.util.SparseArray;
 
-import java.io.File;
 import java.time.Clock;
 
 /**
@@ -30,7 +30,8 @@
 public class ShadowLocaleManagerBackupHelper extends LocaleManagerBackupHelper {
     ShadowLocaleManagerBackupHelper(Context context,
             LocaleManagerService localeManagerService,
-            PackageManagerInternal pmInternal, File stagedLocalesDir, Clock clock) {
-        super(context, localeManagerService, pmInternal, stagedLocalesDir, clock);
+            PackageManagerInternal pmInternal, Clock clock,
+            SparseArray<LocaleManagerBackupHelper.StagedData> stagedData) {
+        super(context, localeManagerService, pmInternal, clock, stagedData);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java b/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java
new file mode 100644
index 0000000..51ddcef
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.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.locksettings;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.PasswordMetrics;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
+import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
+import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.VerifyCredentialResponse;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** atest FrameworksServicesTests:WeakEscrowTokenTests */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class WeakEscrowTokenTests extends BaseLockSettingsServiceTests{
+
+    @Test
+    public void testWeakTokenActivatedImmediatelyIfNoUserPassword()
+            throws RemoteException {
+        mockAutoHardware();
+        final byte[] token = "some-high-entropy-secure-token".getBytes();
+        IWeakEscrowTokenActivatedListener mockListener =
+                mock(IWeakEscrowTokenActivatedListener.Stub.class);
+        long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockListener);
+        assertTrue(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID));
+        assertTrue(mService.isWeakEscrowTokenValid(handle, token, PRIMARY_USER_ID));
+        verify(mockListener).onWeakEscrowTokenActivated(handle, PRIMARY_USER_ID);
+    }
+
+    @Test
+    public void testWeakTokenActivatedLaterWithUserPassword()
+            throws RemoteException {
+        mockAutoHardware();
+        byte[] token = "some-high-entropy-secure-token".getBytes();
+        IWeakEscrowTokenActivatedListener mockListener =
+                mock(IWeakEscrowTokenActivatedListener.Stub.class);
+        LockscreenCredential password = newPassword("password");
+        mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID);
+
+        long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockListener);
+        // Token not activated immediately since user password exists
+        assertFalse(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID));
+        // Activate token (password gets migrated to SP at the same time)
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
+        // Verify token is activated and valid
+        assertTrue(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID));
+        assertTrue(mService.isWeakEscrowTokenValid(handle, token, PRIMARY_USER_ID));
+        verify(mockListener).onWeakEscrowTokenActivated(handle, PRIMARY_USER_ID);
+    }
+
+    @Test
+    public void testWeakTokensRemovedIfCredentialChanged() throws Exception {
+        mockAutoHardware();
+        byte[] token = "some-high-entropy-secure-token".getBytes();
+        IWeakEscrowTokenRemovedListener mockRemoveListener = mockAliveRemoveListener();
+        IWeakEscrowTokenActivatedListener mockActivateListener =
+                mock(IWeakEscrowTokenActivatedListener.Stub.class);
+        LockscreenCredential password = newPassword("password");
+        LockscreenCredential pattern = newPattern("123654");
+        mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID);
+        mService.registerWeakEscrowTokenRemovedListener(mockRemoveListener);
+
+        long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockActivateListener);
+
+        // Activate token
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
+
+        // Verify token removed
+        assertTrue(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID));
+        assertTrue(mLocalService.setLockCredentialWithToken(
+                pattern, handle, token, PRIMARY_USER_ID));
+        assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+        verify(mockRemoveListener).onWeakEscrowTokenRemoved(handle, PRIMARY_USER_ID);
+    }
+
+    @Test
+    public void testWeakTokenRemovedListenerRegistered() throws Exception {
+        mockAutoHardware();
+        IWeakEscrowTokenRemovedListener mockRemoveListener = mockAliveRemoveListener();
+        IWeakEscrowTokenActivatedListener mockActivateListener =
+                mock(IWeakEscrowTokenActivatedListener.Stub.class);
+        byte[] token = "some-high-entropy-secure-token".getBytes();
+        long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockActivateListener);
+
+        mService.registerWeakEscrowTokenRemovedListener(mockRemoveListener);
+        mService.removeWeakEscrowToken(handle, PRIMARY_USER_ID);
+
+        verify(mockRemoveListener).onWeakEscrowTokenRemoved(handle, PRIMARY_USER_ID);
+    }
+
+    @Test
+    public void testWeakTokenRemovedListenerUnregistered() throws Exception {
+        mockAutoHardware();
+        IWeakEscrowTokenRemovedListener mockRemoveListener = mockAliveRemoveListener();
+        IWeakEscrowTokenActivatedListener mockActivateListener =
+                mock(IWeakEscrowTokenActivatedListener.Stub.class);
+        byte[] token0 = "some-high-entropy-secure-token-0".getBytes();
+        byte[] token1 = "some-high-entropy-secure-token-1".getBytes();
+        long handle0 = mService.addWeakEscrowToken(token0, PRIMARY_USER_ID, mockActivateListener);
+        long handle1 = mService.addWeakEscrowToken(token1, PRIMARY_USER_ID, mockActivateListener);
+
+        mService.registerWeakEscrowTokenRemovedListener(mockRemoveListener);
+        mService.removeWeakEscrowToken(handle0, PRIMARY_USER_ID);
+        verify(mockRemoveListener).onWeakEscrowTokenRemoved(handle0, PRIMARY_USER_ID);
+
+        mService.unregisterWeakEscrowTokenRemovedListener(mockRemoveListener);
+        mService.removeWeakEscrowToken(handle1, PRIMARY_USER_ID);
+        verify(mockRemoveListener, never()).onWeakEscrowTokenRemoved(handle1, PRIMARY_USER_ID);
+    }
+
+    @Test
+    public void testUnlockUserWithToken_weakEscrowToken() throws Exception {
+        mockAutoHardware();
+        IWeakEscrowTokenActivatedListener mockActivateListener =
+                mock(IWeakEscrowTokenActivatedListener.Stub.class);
+        LockscreenCredential password = newPassword("password");
+        byte[] token = "some-high-entropy-secure-token".getBytes();
+        mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID);
+        // Disregard any reportPasswordChanged() invocations as part of credential setup.
+        flushHandlerTasks();
+        reset(mDevicePolicyManager);
+
+        long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockActivateListener);
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
+        assertTrue(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID));
+        assertTrue(mService.isWeakEscrowTokenValid(handle, token, PRIMARY_USER_ID));
+
+        mService.onCleanupUser(PRIMARY_USER_ID);
+        assertNull(mLocalService.getUserPasswordMetrics(PRIMARY_USER_ID));
+
+        assertTrue(mLocalService.unlockUserWithToken(handle, token, PRIMARY_USER_ID));
+        assertEquals(PasswordMetrics.computeForCredential(password),
+                mLocalService.getUserPasswordMetrics(PRIMARY_USER_ID));
+    }
+
+    private void mockAutoHardware() {
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(true);
+    }
+
+    private IWeakEscrowTokenRemovedListener mockAliveRemoveListener() {
+        IWeakEscrowTokenRemovedListener mockListener =
+                mock(IWeakEscrowTokenRemovedListener.Stub.class);
+        IBinder mockIBinder = mock(IBinder.class);
+        when(mockIBinder.isBinderAlive()).thenReturn(true);
+        when(mockListener.asBinder()).thenReturn(mockIBinder);
+        return mockListener;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
index 62a2b1b..59f2ca4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -187,7 +187,7 @@
                 /* isFailed */ false,
                 /* isApplied */false,
                 /* stagedSessionErrorCode */
-                PackageInstaller.SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                PackageInstaller.SessionInfo.SESSION_VERIFICATION_FAILED,
                 /* stagedSessionErrorMessage */ "some error");
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
index 26b34fd..304fe5a 100644
--- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
@@ -30,6 +30,7 @@
 import android.hardware.power.stats.State;
 import android.hardware.power.stats.StateResidency;
 import android.hardware.power.stats.StateResidencyResult;
+import android.os.Looper;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -145,12 +146,12 @@
         }
 
         @Override
-        PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath,
-                String meterFilename, String meterCacheFilename,
+        PowerStatsLogger createPowerStatsLogger(Context context, Looper looper,
+                File dataStoragePath, String meterFilename, String meterCacheFilename,
                 String modelFilename, String modelCacheFilename,
                 String residencyFilename, String residencyCacheFilename,
                 IPowerStatsHALWrapper powerStatsHALWrapper) {
-            mPowerStatsLogger = new PowerStatsLogger(context, dataStoragePath,
+            mPowerStatsLogger = new PowerStatsLogger(context, looper, dataStoragePath,
                 meterFilename, meterCacheFilename,
                 modelFilename, modelCacheFilename,
                 residencyFilename, residencyCacheFilename,
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java
index e2a348e..4556a4a 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java
@@ -17,6 +17,7 @@
 package com.android.server.vibrator;
 
 import android.annotation.NonNull;
+import android.content.Context;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
@@ -24,37 +25,8 @@
 /** Fake implementation of {@link Vibrator} for service tests. */
 final class FakeVibrator extends Vibrator {
 
-    private int mDefaultHapticFeedbackIntensity = Vibrator.VIBRATION_INTENSITY_MEDIUM;
-    private int mDefaultNotificationIntensity = Vibrator.VIBRATION_INTENSITY_MEDIUM;
-    private int mDefaultRingIntensity = Vibrator.VIBRATION_INTENSITY_MEDIUM;
-
-    @Override
-    public int getDefaultHapticFeedbackIntensity() {
-        return mDefaultHapticFeedbackIntensity;
-    }
-
-    @Override
-    public int getDefaultNotificationVibrationIntensity() {
-        return mDefaultNotificationIntensity;
-    }
-
-    @Override
-    public int getDefaultRingVibrationIntensity() {
-        return mDefaultRingIntensity;
-    }
-
-    public void setDefaultHapticFeedbackIntensity(
-            @VibrationIntensity int defaultHapticFeedbackIntensity) {
-        mDefaultHapticFeedbackIntensity = defaultHapticFeedbackIntensity;
-    }
-
-    public void setDefaultNotificationVibrationIntensity(
-            @VibrationIntensity int defaultNotificationIntensity) {
-        mDefaultNotificationIntensity = defaultNotificationIntensity;
-    }
-
-    public void setDefaultRingVibrationIntensity(@VibrationIntensity int defaultRingIntensity) {
-        mDefaultRingIntensity = defaultRingIntensity;
+    FakeVibrator(Context context) {
+        super(context);
     }
 
     @Override
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java
index 59c0b0e..6369dbc 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java
@@ -16,6 +16,14 @@
 
 package com.android.server.vibrator;
 
+import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
+import static android.os.VibrationAttributes.USAGE_RINGTONE;
+import static android.os.VibrationAttributes.USAGE_TOUCH;
+import static android.os.Vibrator.VIBRATION_INTENSITY_HIGH;
+import static android.os.Vibrator.VIBRATION_INTENSITY_LOW;
+import static android.os.Vibrator.VIBRATION_INTENSITY_MEDIUM;
+import static android.os.Vibrator.VIBRATION_INTENSITY_OFF;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 
@@ -24,7 +32,6 @@
 import static org.mockito.Mockito.when;
 
 import android.content.ContentResolver;
-import android.content.Context;
 import android.content.ContextWrapper;
 import android.os.Handler;
 import android.os.IExternalVibratorService;
@@ -37,6 +44,7 @@
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
 import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationConfig;
 import android.os.vibrator.VibrationEffectSegment;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
@@ -68,29 +76,31 @@
     @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
 
     @Mock private PowerManagerInternal mPowerManagerInternalMock;
+    @Mock private VibrationConfig mVibrationConfigMock;
 
     private TestLooper mTestLooper;
     private ContextWrapper mContextSpy;
-    private FakeVibrator mFakeVibrator;
     private VibrationSettings mVibrationSettings;
     private VibrationScaler mVibrationScaler;
 
     @Before
     public void setUp() throws Exception {
         mTestLooper = new TestLooper();
-        mFakeVibrator = new FakeVibrator();
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
 
         ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
         when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
-        when(mContextSpy.getSystemService(eq(Context.VIBRATOR_SERVICE))).thenReturn(mFakeVibrator);
 
         LocalServices.removeServiceForTest(PowerManagerInternal.class);
         LocalServices.addService(PowerManagerInternal.class, mPowerManagerInternalMock);
 
+        Settings.System.putInt(contentResolver, Settings.System.HAPTIC_FEEDBACK_ENABLED, 1);
+        Settings.System.putInt(contentResolver, Settings.System.VIBRATE_WHEN_RINGING, 1);
+
         mVibrationSettings = new VibrationSettings(
-                mContextSpy, new Handler(mTestLooper.getLooper()));
+                mContextSpy, new Handler(mTestLooper.getLooper()), mVibrationConfigMock);
         mVibrationScaler = new VibrationScaler(mContextSpy, mVibrationSettings);
+
         mVibrationSettings.onSystemReady();
     }
 
@@ -101,91 +111,80 @@
 
     @Test
     public void testGetExternalVibrationScale() {
-        mFakeVibrator.setDefaultHapticFeedbackIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_HIGH);
+        setDefaultIntensity(USAGE_TOUCH, Vibrator.VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
         assertEquals(IExternalVibratorService.SCALE_VERY_HIGH,
-                mVibrationScaler.getExternalVibrationScale(VibrationAttributes.USAGE_TOUCH));
+                mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
 
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
         assertEquals(IExternalVibratorService.SCALE_HIGH,
-                mVibrationScaler.getExternalVibrationScale(VibrationAttributes.USAGE_TOUCH));
+                mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
 
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_LOW);
         assertEquals(IExternalVibratorService.SCALE_NONE,
-                mVibrationScaler.getExternalVibrationScale(VibrationAttributes.USAGE_TOUCH));
+                mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
 
-        mFakeVibrator.setDefaultHapticFeedbackIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM);
+        setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_MEDIUM);
         assertEquals(IExternalVibratorService.SCALE_LOW,
-                mVibrationScaler.getExternalVibrationScale(VibrationAttributes.USAGE_TOUCH));
+                mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
 
-        mFakeVibrator.setDefaultHapticFeedbackIntensity(Vibrator.VIBRATION_INTENSITY_HIGH);
+        setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_HIGH);
         assertEquals(IExternalVibratorService.SCALE_VERY_LOW,
-                mVibrationScaler.getExternalVibrationScale(VibrationAttributes.USAGE_TOUCH));
+                mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
 
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
         // Unexpected vibration intensity will be treated as SCALE_NONE.
         assertEquals(IExternalVibratorService.SCALE_NONE,
-                mVibrationScaler.getExternalVibrationScale(VibrationAttributes.USAGE_TOUCH));
+                mVibrationScaler.getExternalVibrationScale(USAGE_TOUCH));
     }
 
     @Test
     public void scale_withPrebakedSegment_setsEffectStrengthBasedOnSettings() {
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_HIGH);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
         PrebakedSegment effect = new PrebakedSegment(VibrationEffect.EFFECT_CLICK,
                 /* shouldFallback= */ false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
 
-        PrebakedSegment scaled = mVibrationScaler.scale(
-                effect, VibrationAttributes.USAGE_NOTIFICATION);
+        PrebakedSegment scaled = mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
         assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
 
         setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
-        scaled = mVibrationScaler.scale(effect, VibrationAttributes.USAGE_NOTIFICATION);
+                VIBRATION_INTENSITY_MEDIUM);
+        scaled = mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
         assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM);
 
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_LOW);
-        scaled = mVibrationScaler.scale(effect, VibrationAttributes.USAGE_NOTIFICATION);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
+        scaled = mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
         assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT);
 
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-        scaled = mVibrationScaler.scale(effect, VibrationAttributes.USAGE_NOTIFICATION);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+        scaled = mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
         // Unexpected intensity setting will be mapped to STRONG.
         assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
     }
 
     @Test
     public void scale_withPrebakedEffect_setsEffectStrengthBasedOnSettings() {
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_HIGH);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
         VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
 
         PrebakedSegment scaled = getFirstSegment(mVibrationScaler.scale(
-                effect, VibrationAttributes.USAGE_NOTIFICATION));
+                effect, USAGE_NOTIFICATION));
         assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
 
         setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
+                VIBRATION_INTENSITY_MEDIUM);
         scaled = getFirstSegment(mVibrationScaler.scale(
-                effect, VibrationAttributes.USAGE_NOTIFICATION));
+                effect, USAGE_NOTIFICATION));
         assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM);
 
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
         scaled = getFirstSegment(mVibrationScaler.scale(
-                effect, VibrationAttributes.USAGE_NOTIFICATION));
+                effect, USAGE_NOTIFICATION));
         assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT);
 
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
         scaled = getFirstSegment(mVibrationScaler.scale(
-                effect, VibrationAttributes.USAGE_NOTIFICATION));
+                effect, USAGE_NOTIFICATION));
         // Unexpected intensity setting will be mapped to STRONG.
         assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
     }
@@ -193,81 +192,77 @@
     @Test
     public void scale_withOneShotAndWaveform_resolvesAmplitude() {
         // No scale, default amplitude still resolved
-        mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_LOW);
+        setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
 
         StepSegment resolved = getFirstSegment(mVibrationScaler.scale(
                 VibrationEffect.createOneShot(10, VibrationEffect.DEFAULT_AMPLITUDE),
-                VibrationAttributes.USAGE_RINGTONE));
+                USAGE_RINGTONE));
         assertTrue(resolved.getAmplitude() > 0);
 
         resolved = getFirstSegment(mVibrationScaler.scale(
                 VibrationEffect.createWaveform(new long[]{10},
                         new int[]{VibrationEffect.DEFAULT_AMPLITUDE}, -1),
-                VibrationAttributes.USAGE_RINGTONE));
+                USAGE_RINGTONE));
         assertTrue(resolved.getAmplitude() > 0);
     }
 
     @Test
     public void scale_withOneShotAndWaveform_scalesAmplitude() {
-        mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_HIGH);
-        mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_HIGH);
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_LOW);
-        mFakeVibrator.setDefaultHapticFeedbackIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
+        setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
+        setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
+        setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_MEDIUM);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
 
         StepSegment scaled = getFirstSegment(mVibrationScaler.scale(
-                VibrationEffect.createOneShot(128, 128), VibrationAttributes.USAGE_RINGTONE));
+                VibrationEffect.createOneShot(128, 128), USAGE_RINGTONE));
         // Ringtone scales up.
         assertTrue(scaled.getAmplitude() > 0.5);
 
         scaled = getFirstSegment(mVibrationScaler.scale(
                 VibrationEffect.createWaveform(new long[]{128}, new int[]{128}, -1),
-                VibrationAttributes.USAGE_NOTIFICATION));
+                USAGE_NOTIFICATION));
         // Notification scales down.
         assertTrue(scaled.getAmplitude() < 0.5);
 
         scaled = getFirstSegment(mVibrationScaler.scale(VibrationEffect.createOneShot(128, 128),
-                VibrationAttributes.USAGE_TOUCH));
+                USAGE_TOUCH));
         // Haptic feedback does not scale.
         assertEquals(128f / 255, scaled.getAmplitude(), 1e-5);
     }
 
     @Test
     public void scale_withComposed_scalesPrimitives() {
-        mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_HIGH);
-        mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_HIGH);
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_LOW);
-        mFakeVibrator.setDefaultHapticFeedbackIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
+        setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
+        setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
+        setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_MEDIUM);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
 
         VibrationEffect composed = VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f).compose();
 
-        PrimitiveSegment scaled = getFirstSegment(mVibrationScaler.scale(composed,
-                VibrationAttributes.USAGE_RINGTONE));
+        PrimitiveSegment scaled = getFirstSegment(mVibrationScaler.scale(composed, USAGE_RINGTONE));
         // Ringtone scales up.
         assertTrue(scaled.getScale() > 0.5f);
 
-        scaled = getFirstSegment(mVibrationScaler.scale(composed,
-                VibrationAttributes.USAGE_NOTIFICATION));
+        scaled = getFirstSegment(mVibrationScaler.scale(composed, USAGE_NOTIFICATION));
         // Notification scales down.
         assertTrue(scaled.getScale() < 0.5f);
 
-        scaled = getFirstSegment(mVibrationScaler.scale(composed, VibrationAttributes.USAGE_TOUCH));
+        scaled = getFirstSegment(mVibrationScaler.scale(composed, USAGE_TOUCH));
         // Haptic feedback does not scale.
         assertEquals(0.5, scaled.getScale(), 1e-5);
     }
 
+    private void setDefaultIntensity(@VibrationAttributes.Usage int usage,
+            @Vibrator.VibrationIntensity int intensity) {
+        when(mVibrationConfigMock.getDefaultVibrationIntensity(eq(usage))).thenReturn(intensity);
+    }
+
     private <T extends VibrationEffectSegment> T getFirstSegment(VibrationEffect.Composed effect) {
         return (T) effect.getSegments().get(0);
     }
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
index ab9fbb5..ff59d0f 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -16,9 +16,11 @@
 
 package com.android.server.vibrator;
 
+import static android.os.VibrationAttributes.USAGE_ACCESSIBILITY;
 import static android.os.VibrationAttributes.USAGE_ALARM;
 import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
 import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
+import static android.os.VibrationAttributes.USAGE_MEDIA;
 import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
 import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION;
 import static android.os.VibrationAttributes.USAGE_RINGTONE;
@@ -35,6 +37,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.spy;
@@ -45,7 +48,6 @@
 
 import android.app.ActivityManager;
 import android.content.ContentResolver;
-import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
 import android.media.AudioManager;
@@ -55,7 +57,9 @@
 import android.os.UserHandle;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
+import android.os.Vibrator;
 import android.os.test.TestLooper;
+import android.os.vibrator.VibrationConfig;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 
@@ -87,6 +91,19 @@
     private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
             .setBatterySaverEnabled(true).build();
 
+    private static final int[] ALL_USAGES = new int[] {
+            USAGE_UNKNOWN,
+            USAGE_ACCESSIBILITY,
+            USAGE_ALARM,
+            USAGE_COMMUNICATION_REQUEST,
+            USAGE_HARDWARE_FEEDBACK,
+            USAGE_MEDIA,
+            USAGE_NOTIFICATION,
+            USAGE_PHYSICAL_EMULATION,
+            USAGE_RINGTONE,
+            USAGE_TOUCH,
+    };
+
     @Rule
     public MockitoRule mMockitoRule = MockitoJUnit.rule();
     @Rule
@@ -96,23 +113,23 @@
     private VibrationSettings.OnVibratorSettingsChanged mListenerMock;
     @Mock
     private PowerManagerInternal mPowerManagerInternalMock;
+    @Mock
+    private VibrationConfig mVibrationConfigMock;
 
     private TestLooper mTestLooper;
     private ContextWrapper mContextSpy;
     private AudioManager mAudioManager;
-    private FakeVibrator mFakeVibrator;
     private VibrationSettings mVibrationSettings;
     private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
 
     @Before
     public void setUp() throws Exception {
         mTestLooper = new TestLooper();
-        mFakeVibrator = new FakeVibrator();
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
 
         ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
         when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
-        when(mContextSpy.getSystemService(eq(Context.VIBRATOR_SERVICE))).thenReturn(mFakeVibrator);
+
         doAnswer(invocation -> {
             mRegisteredPowerModeListener = invocation.getArgument(0);
             return null;
@@ -121,16 +138,18 @@
         LocalServices.removeServiceForTest(PowerManagerInternal.class);
         LocalServices.addService(PowerManagerInternal.class, mPowerManagerInternalMock);
 
+        setDefaultIntensity(VIBRATION_INTENSITY_MEDIUM);
         mAudioManager = mContextSpy.getSystemService(AudioManager.class);
         mVibrationSettings = new VibrationSettings(mContextSpy,
-                new Handler(mTestLooper.getLooper()));
-        mVibrationSettings.onSystemReady();
+                new Handler(mTestLooper.getLooper()), mVibrationConfigMock);
 
         // Simulate System defaults.
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 1);
         setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0);
         setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
         setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
         setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+        mVibrationSettings.onSystemReady();
     }
 
     @After
@@ -145,12 +164,15 @@
         setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
         setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
         setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
+        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
         setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.MEDIA_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
         setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 0);
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
         setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
 
-        verify(mListenerMock, times(7)).onChange();
+        verify(mListenerMock, times(10)).onChange();
     }
 
     @Test
@@ -192,9 +214,7 @@
                 UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
 
         for (int usage : expectedAllowedVibrations) {
-            assertNull("Error for usage " + VibrationAttributes.usageToString(usage),
-                    mVibrationSettings.shouldIgnoreVibration(UID,
-                            VibrationAttributes.createForUsage(usage)));
+            assertVibrationNotIgnoredForUsage(usage);
         }
     }
 
@@ -209,10 +229,7 @@
                 UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
 
         for (int usage : expectedIgnoredVibrations) {
-            assertEquals("Error for usage " + VibrationAttributes.usageToString(usage),
-                    Vibration.Status.IGNORED_BACKGROUND,
-                    mVibrationSettings.shouldIgnoreVibration(UID,
-                            VibrationAttributes.createForUsage(usage)));
+            assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_BACKGROUND);
         }
     }
 
@@ -221,10 +238,9 @@
         mVibrationSettings.mUidObserver.onUidStateChanged(
                 UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
 
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_TOUCH)));
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_ALARM)));
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsage(usage);
+        }
     }
 
     @Test
@@ -238,9 +254,7 @@
         mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
 
         for (int usage : expectedAllowedVibrations) {
-            assertNull("Error for usage " + VibrationAttributes.usageToString(usage),
-                    mVibrationSettings.shouldIgnoreVibration(UID,
-                            VibrationAttributes.createForUsage(usage)));
+            assertVibrationNotIgnoredForUsage(usage);
         }
     }
 
@@ -257,10 +271,7 @@
         mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
 
         for (int usage : expectedIgnoredVibrations) {
-            assertEquals("Error for usage " + VibrationAttributes.usageToString(usage),
-                    Vibration.Status.IGNORED_FOR_POWER,
-                    mVibrationSettings.shouldIgnoreVibration(UID,
-                            VibrationAttributes.createForUsage(usage)));
+            assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_POWER);
         }
     }
 
@@ -268,130 +279,130 @@
     public void shouldIgnoreVibration_notInBatterySaverMode_allowsAnyUsage() {
         mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
 
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_TOUCH)));
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_COMMUNICATION_REQUEST)));
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsage(usage);
+        }
     }
 
     @Test
     public void shouldIgnoreVibration_withRingerModeSilent_ignoresRingtoneAndTouch() {
         // Vibrating settings on are overruled by ringer mode.
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 1);
         setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
-        setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
+        setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 1);
         setRingerMode(AudioManager.RINGER_MODE_SILENT);
 
-        assertEquals(Vibration.Status.IGNORED_FOR_RINGER_MODE,
-                mVibrationSettings.shouldIgnoreVibration(UID,
-                        VibrationAttributes.createForUsage(USAGE_RINGTONE)));
-        assertEquals(Vibration.Status.IGNORED_FOR_RINGER_MODE,
-                mVibrationSettings.shouldIgnoreVibration(UID,
-                        VibrationAttributes.createForUsage(USAGE_TOUCH)));
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_COMMUNICATION_REQUEST)));
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_HARDWARE_FEEDBACK)));
+        for (int usage : ALL_USAGES) {
+            if (usage == USAGE_RINGTONE || usage == USAGE_TOUCH) {
+                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_RINGER_MODE);
+            } else {
+                assertVibrationNotIgnoredForUsage(usage);
+            }
+        }
     }
 
     @Test
     public void shouldIgnoreVibration_withRingerModeVibrate_allowsAllVibrations() {
-        // Vibrating settings off are overruled by ringer mode.
-        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
-        setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
         setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
 
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_TOUCH)));
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_PHYSICAL_EMULATION)));
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_RINGTONE)));
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsage(usage);
+        }
     }
 
     @Test
-    public void shouldIgnoreVibration_withRingerModeNormalAndRingSettingsOff_ignoresRingtoneOnly() {
-        // Vibrating settings off are respected for normal ringer mode.
-        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
-        setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
+    public void shouldIgnoreVibration_withRingerModeNormal_allowsAllVibrations() {
         setRingerMode(AudioManager.RINGER_MODE_NORMAL);
 
-        assertEquals(Vibration.Status.IGNORED_FOR_RINGER_MODE,
-                mVibrationSettings.shouldIgnoreVibration(UID,
-                        VibrationAttributes.createForUsage(USAGE_RINGTONE)));
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_TOUCH)));
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_NOTIFICATION)));
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsage(usage);
+        }
     }
 
     @Test
-    public void shouldIgnoreVibration_withRingerModeNormalAndRingSettingsOn_allowsAllVibrations() {
+    public void shouldIgnoreVibration_withRingSettingsOff_disableRingtoneVibrations() {
+        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+        setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
+
+        for (int usage : ALL_USAGES) {
+            if (usage == USAGE_RINGTONE) {
+                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+            } else {
+                assertVibrationNotIgnoredForUsage(usage);
+            }
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_withRingSettingsOn_allowsAllVibrations() {
         setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
         setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
-        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
 
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_TOUCH)));
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_RINGTONE)));
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_ALARM)));
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsage(usage);
+        }
     }
 
     @Test
-    public void shouldIgnoreVibration_withRingerModeNormalAndRampingRingerOn_allowsAllVibrations() {
+    public void shouldIgnoreVibration_withRampingRingerOn_allowsAllVibrations() {
         setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
         setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 1);
-        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
 
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_TOUCH)));
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_RINGTONE)));
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_COMMUNICATION_REQUEST)));
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsage(usage);
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_withHapticFeedbackDisabled_ignoresTouchVibration() {
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 0);
+
+        for (int usage : ALL_USAGES) {
+            if (usage == USAGE_TOUCH) {
+                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+            } else {
+                assertVibrationNotIgnoredForUsage(usage);
+            }
+        }
     }
 
     @Test
     public void shouldIgnoreVibration_withHapticFeedbackSettingsOff_ignoresTouchVibration() {
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
 
-        assertEquals(Vibration.Status.IGNORED_FOR_SETTINGS,
-                mVibrationSettings.shouldIgnoreVibration(UID,
-                        VibrationAttributes.createForUsage(USAGE_TOUCH)));
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_RINGTONE)));
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_HARDWARE_FEEDBACK)));
+        for (int usage : ALL_USAGES) {
+            if (usage == USAGE_TOUCH) {
+                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+            } else {
+                assertVibrationNotIgnoredForUsage(usage);
+            }
+        }
     }
 
     @Test
     public void shouldIgnoreVibration_withHardwareFeedbackSettingsOff_ignoresHardwareVibrations() {
         setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
 
-        assertEquals(Vibration.Status.IGNORED_FOR_SETTINGS,
-                mVibrationSettings.shouldIgnoreVibration(UID,
-                        VibrationAttributes.createForUsage(USAGE_HARDWARE_FEEDBACK)));
-        assertEquals(Vibration.Status.IGNORED_FOR_SETTINGS,
-                mVibrationSettings.shouldIgnoreVibration(UID,
-                        VibrationAttributes.createForUsage(USAGE_PHYSICAL_EMULATION)));
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_TOUCH)));
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_NOTIFICATION)));
+        for (int usage : ALL_USAGES) {
+            if (usage == USAGE_HARDWARE_FEEDBACK || usage == USAGE_PHYSICAL_EMULATION) {
+                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+            } else {
+                assertVibrationNotIgnoredForUsage(usage);
+            }
+        }
     }
 
     @Test
     public void shouldIgnoreVibration_withNotificationSettingsOff_ignoresNotificationVibrations() {
         setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
 
-        assertEquals(Vibration.Status.IGNORED_FOR_SETTINGS,
-                mVibrationSettings.shouldIgnoreVibration(UID,
-                        VibrationAttributes.createForUsage(USAGE_NOTIFICATION)));
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_ALARM)));
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_RINGTONE)));
+        for (int usage : ALL_USAGES) {
+            if (usage == USAGE_NOTIFICATION) {
+                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+            } else {
+                assertVibrationNotIgnoredForUsage(usage);
+            }
+        }
     }
 
     @Test
@@ -402,15 +413,13 @@
         setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
         setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
 
-        assertEquals(Vibration.Status.IGNORED_FOR_SETTINGS,
-                mVibrationSettings.shouldIgnoreVibration(UID,
-                        VibrationAttributes.createForUsage(USAGE_RINGTONE)));
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_NOTIFICATION)));
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_ALARM)));
-        assertNull(mVibrationSettings.shouldIgnoreVibration(UID,
-                VibrationAttributes.createForUsage(USAGE_TOUCH)));
+        for (int usage : ALL_USAGES) {
+            if (usage == USAGE_RINGTONE) {
+                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+            } else {
+                assertVibrationNotIgnoredForUsage(usage);
+            }
+        }
     }
 
     @Test
@@ -423,90 +432,40 @@
     }
 
     @Test
-    public void getDefaultIntensity_beforeSystemReady_returnsMediumToAllExceptAlarm() {
-        mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_HIGH);
-        mFakeVibrator.setDefaultNotificationVibrationIntensity(VIBRATION_INTENSITY_HIGH);
-        mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_HIGH);
-
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+    public void getDefaultIntensity_returnsIntensityFromVibratorConfig() {
+        setDefaultIntensity(VIBRATION_INTENSITY_HIGH);
+        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
         setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
-
-        VibrationSettings vibrationSettings = new VibrationSettings(mContextSpy,
-                new Handler(mTestLooper.getLooper()));
-
-        assertEquals(VIBRATION_INTENSITY_HIGH,
-                vibrationSettings.getDefaultIntensity(USAGE_ALARM));
-        assertEquals(VIBRATION_INTENSITY_MEDIUM,
-                vibrationSettings.getDefaultIntensity(USAGE_TOUCH));
-        assertEquals(VIBRATION_INTENSITY_MEDIUM,
-                vibrationSettings.getDefaultIntensity(USAGE_HARDWARE_FEEDBACK));
-        assertEquals(VIBRATION_INTENSITY_MEDIUM,
-                vibrationSettings.getDefaultIntensity(USAGE_PHYSICAL_EMULATION));
-        assertEquals(VIBRATION_INTENSITY_MEDIUM,
-                vibrationSettings.getDefaultIntensity(USAGE_NOTIFICATION));
-        assertEquals(VIBRATION_INTENSITY_MEDIUM,
-                vibrationSettings.getDefaultIntensity(USAGE_UNKNOWN));
-        assertEquals(VIBRATION_INTENSITY_MEDIUM,
-                vibrationSettings.getDefaultIntensity(USAGE_RINGTONE));
-    }
-
-    @Test
-    public void getDefaultIntensity_returnsIntensityFromVibratorService() {
-        mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_HIGH);
-        mFakeVibrator.setDefaultNotificationVibrationIntensity(VIBRATION_INTENSITY_MEDIUM);
-        mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_LOW);
-
         setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.MEDIA_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
         setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
 
-        assertEquals(VIBRATION_INTENSITY_HIGH,
-                mVibrationSettings.getDefaultIntensity(USAGE_ALARM));
-        assertEquals(VIBRATION_INTENSITY_HIGH,
-                mVibrationSettings.getDefaultIntensity(USAGE_TOUCH));
-        assertEquals(VIBRATION_INTENSITY_HIGH,
-                mVibrationSettings.getDefaultIntensity(USAGE_HARDWARE_FEEDBACK));
-        assertEquals(VIBRATION_INTENSITY_HIGH,
-                mVibrationSettings.getDefaultIntensity(USAGE_PHYSICAL_EMULATION));
-        assertEquals(VIBRATION_INTENSITY_MEDIUM,
-                mVibrationSettings.getDefaultIntensity(USAGE_NOTIFICATION));
-        assertEquals(VIBRATION_INTENSITY_MEDIUM,
-                mVibrationSettings.getDefaultIntensity(USAGE_UNKNOWN));
-        assertEquals(VIBRATION_INTENSITY_LOW,
-                mVibrationSettings.getDefaultIntensity(USAGE_RINGTONE));
+        for (int usage : ALL_USAGES) {
+            assertEquals(VIBRATION_INTENSITY_HIGH, mVibrationSettings.getDefaultIntensity(usage));
+        }
     }
 
     @Test
     public void getCurrentIntensity_returnsIntensityFromSettings() {
-        mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_OFF);
-        mFakeVibrator.setDefaultNotificationVibrationIntensity(VIBRATION_INTENSITY_OFF);
-        mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_OFF);
-
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
+        setDefaultIntensity(VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_LOW);
         setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                VIBRATION_INTENSITY_MEDIUM);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.MEDIA_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
         setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
 
-        assertEquals(VIBRATION_INTENSITY_HIGH, mVibrationSettings.getCurrentIntensity(USAGE_ALARM));
-        assertEquals(VIBRATION_INTENSITY_HIGH, mVibrationSettings.getCurrentIntensity(USAGE_TOUCH));
-        assertEquals(VIBRATION_INTENSITY_LOW,
-                mVibrationSettings.getCurrentIntensity(USAGE_HARDWARE_FEEDBACK));
-        assertEquals(VIBRATION_INTENSITY_LOW,
-                mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION));
-        assertEquals(VIBRATION_INTENSITY_MEDIUM,
-                mVibrationSettings.getCurrentIntensity(USAGE_NOTIFICATION));
-        assertEquals(VIBRATION_INTENSITY_MEDIUM,
-                mVibrationSettings.getCurrentIntensity(USAGE_UNKNOWN));
-        assertEquals(VIBRATION_INTENSITY_LOW,
-                mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE));
+        for (int usage : ALL_USAGES) {
+            assertEquals(errorMessageForUsage(usage),
+                    VIBRATION_INTENSITY_LOW,
+                    mVibrationSettings.getCurrentIntensity(usage));
+        }
     }
 
     @Test
     public void getCurrentIntensity_updateTriggeredAfterUserSwitched() {
-        mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_OFF);
+        setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_OFF);
         setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
         assertEquals(VIBRATION_INTENSITY_HIGH,
                 mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE));
@@ -524,8 +483,9 @@
 
     @Test
     public void getCurrentIntensity_noHardwareFeedbackValueUsesHapticFeedbackValue() {
-        mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_MEDIUM);
+        setDefaultIntensity(USAGE_HARDWARE_FEEDBACK, VIBRATION_INTENSITY_MEDIUM);
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+        mVibrationSettings.updateSettings();
         assertEquals(VIBRATION_INTENSITY_OFF, mVibrationSettings.getCurrentIntensity(USAGE_TOUCH));
         // If haptic feedback is off, fallback to default value.
         assertEquals(VIBRATION_INTENSITY_MEDIUM,
@@ -533,15 +493,11 @@
         assertEquals(VIBRATION_INTENSITY_MEDIUM,
                 mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION));
 
-        // Switching user is not working with FakeSettingsProvider.
-        // Testing the broadcast flow manually.
-        Settings.System.putIntForUser(mContextSpy.getContentResolver(),
-                Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH,
-                UserHandle.USER_CURRENT);
-        mVibrationSettings.mUserReceiver.onReceive(mContextSpy,
-                new Intent(Intent.ACTION_USER_SWITCHED));
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
+        mVibrationSettings.updateSettings();
         assertEquals(VIBRATION_INTENSITY_HIGH,
                 mVibrationSettings.getCurrentIntensity(USAGE_TOUCH));
+        // If haptic feedback is on, fallback to that value.
         assertEquals(VIBRATION_INTENSITY_HIGH,
                 mVibrationSettings.getCurrentIntensity(USAGE_HARDWARE_FEEDBACK));
         assertEquals(VIBRATION_INTENSITY_HIGH,
@@ -557,6 +513,33 @@
         assertNotNull(mVibrationSettings.getFallbackEffect(VibrationEffect.EFFECT_DOUBLE_CLICK));
     }
 
+    private void assertVibrationIgnoredForUsage(@VibrationAttributes.Usage int usage,
+            Vibration.Status expectedStatus) {
+        assertEquals(errorMessageForUsage(usage),
+                expectedStatus,
+                mVibrationSettings.shouldIgnoreVibration(UID,
+                        VibrationAttributes.createForUsage(usage)));
+    }
+
+    private void assertVibrationNotIgnoredForUsage(@VibrationAttributes.Usage int usage) {
+        assertNull(errorMessageForUsage(usage),
+                mVibrationSettings.shouldIgnoreVibration(UID,
+                        VibrationAttributes.createForUsage(usage)));
+    }
+
+    private String errorMessageForUsage(int usage) {
+        return "Error for usage " + VibrationAttributes.usageToString(usage);
+    }
+
+    private void setDefaultIntensity(@Vibrator.VibrationIntensity int intensity) {
+        when(mVibrationConfigMock.getDefaultVibrationIntensity(anyInt())).thenReturn(intensity);
+    }
+
+    private void setDefaultIntensity(@VibrationAttributes.Usage int usage,
+            @Vibrator.VibrationIntensity int intensity) {
+        when(mVibrationConfigMock.getDefaultVibrationIntensity(eq(usage))).thenReturn(intensity);
+    }
+
     private void setUserSetting(String settingName, int value) {
         Settings.System.putIntForUser(
                 mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
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 bfceb9a..5dd44ff 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -44,11 +44,13 @@
 import android.os.SystemClock;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
+import android.os.Vibrator;
 import android.os.test.TestLooper;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
 import android.os.vibrator.RampSegment;
 import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationConfig;
 import android.os.vibrator.VibrationEffectSegment;
 import android.platform.test.annotations.LargeTest;
 import android.platform.test.annotations.Presubmit;
@@ -101,6 +103,8 @@
     private IBinder mVibrationToken;
     @Mock
     private IBatteryStats mIBatteryStatsMock;
+    @Mock
+    private VibrationConfig mVibrationConfigMock;
 
     private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
     private VibrationSettings mVibrationSettings;
@@ -113,9 +117,13 @@
     public void setUp() throws Exception {
         mTestLooper = new TestLooper();
 
+        when(mVibrationConfigMock.getDefaultVibrationIntensity(anyInt()))
+                .thenReturn(Vibrator.VIBRATION_INTENSITY_MEDIUM);
+        when(mVibrationConfigMock.getRampStepDurationMs()).thenReturn(TEST_RAMP_STEP_DURATION);
+
         Context context = InstrumentationRegistry.getContext();
         mVibrationSettings = new VibrationSettings(context, new Handler(mTestLooper.getLooper()),
-                /* rampDownDuration= */ 0, TEST_RAMP_STEP_DURATION);
+                mVibrationConfigMock);
         mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
         mWakeLock = context.getSystemService(
                 PowerManager.class).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
@@ -1111,9 +1119,7 @@
 
     @Test
     public void vibrate_waveformWithRampDown_addsRampDownAfterVibrationCompleted() {
-        int rampDownDuration = 15;
-        mVibrationSettings = new VibrationSettings(InstrumentationRegistry.getContext(),
-                new Handler(mTestLooper.getLooper()), rampDownDuration, TEST_RAMP_STEP_DURATION);
+        when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
         mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
 
@@ -1139,9 +1145,7 @@
 
     @Test
     public void vibrate_waveformWithRampDown_triggersCallbackWhenOriginalVibrationEnds() {
-        int rampDownDuration = 10_000;
-        mVibrationSettings = new VibrationSettings(InstrumentationRegistry.getContext(),
-                new Handler(mTestLooper.getLooper()), rampDownDuration, TEST_RAMP_STEP_DURATION);
+        when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(10_000);
         mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
 
@@ -1174,9 +1178,7 @@
     @Test
     public void vibrate_waveformCancelledWithRampDown_addsRampDownAfterVibrationCancelled()
             throws Exception {
-        int rampDownDuration = 15;
-        mVibrationSettings = new VibrationSettings(InstrumentationRegistry.getContext(),
-                new Handler(mTestLooper.getLooper()), rampDownDuration, TEST_RAMP_STEP_DURATION);
+        when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
         mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
 
@@ -1202,9 +1204,7 @@
 
     @Test
     public void vibrate_predefinedWithRampDown_doesNotAddRampDown() {
-        int rampDownDuration = 15;
-        mVibrationSettings = new VibrationSettings(InstrumentationRegistry.getContext(),
-                new Handler(mTestLooper.getLooper()), rampDownDuration, TEST_RAMP_STEP_DURATION);
+        when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
         mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
@@ -1224,9 +1224,7 @@
 
     @Test
     public void vibrate_composedWithRampDown_doesNotAddRampDown() {
-        int rampDownDuration = 15;
-        mVibrationSettings = new VibrationSettings(InstrumentationRegistry.getContext(),
-                new Handler(mTestLooper.getLooper()), rampDownDuration, TEST_RAMP_STEP_DURATION);
+        when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
         mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
                 IVibrator.CAP_COMPOSE_EFFECTS);
@@ -1251,9 +1249,7 @@
 
     @Test
     public void vibrate_pwleWithRampDown_doesNotAddRampDown() {
-        int rampDownDuration = 15;
-        mVibrationSettings = new VibrationSettings(InstrumentationRegistry.getContext(),
-                new Handler(mTestLooper.getLooper()), rampDownDuration, TEST_RAMP_STEP_DURATION);
+        when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15);
         mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
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 c0f7596..b0bdaf0 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -155,12 +156,13 @@
     @Before
     public void setUp() throws Exception {
         mTestLooper = new TestLooper();
-        mVibrator = new FakeVibrator();
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
         InputManager inputManager = InputManager.resetInstance(mIInputManagerMock);
 
         ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
         when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
+
+        mVibrator = new FakeVibrator(mContextSpy);
         when(mContextSpy.getSystemService(eq(Context.VIBRATOR_SERVICE))).thenReturn(mVibrator);
         when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager);
         when(mContextSpy.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManagerMock);
@@ -175,8 +177,13 @@
         }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any());
 
         setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 1);
+        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_MEDIUM);
         setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
                 Vibrator.VIBRATION_INTENSITY_MEDIUM);
+        setUserSetting(Settings.System.MEDIA_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_MEDIUM);
         setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
                 Vibrator.VIBRATION_INTENSITY_MEDIUM);
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
@@ -437,7 +444,7 @@
                 UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS));
 
         PrebakedSegment expected = new PrebakedSegment(
-                VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG);
+                VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
 
         // Only vibrators 1 and 3 have always-on capabilities.
         assertEquals(mVibratorProviders.get(1).getAlwaysOnEffect(1), expected);
@@ -461,10 +468,10 @@
                 UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS));
 
         PrebakedSegment expectedClick = new PrebakedSegment(
-                VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG);
+                VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
 
         PrebakedSegment expectedTick = new PrebakedSegment(
-                VibrationEffect.EFFECT_TICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG);
+                VibrationEffect.EFFECT_TICK, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
 
         // Enables click on vibrator 1 and tick on vibrator 2 only.
         assertEquals(mVibratorProviders.get(1).getAlwaysOnEffect(1), expectedClick);
@@ -539,7 +546,6 @@
     public void vibrate_withRingtone_usesRingtoneSettings() throws Exception {
         mockVibrators(1);
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
-        mVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM);
         fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK,
                 VibrationEffect.EFFECT_HEAVY_CLICK, VibrationEffect.EFFECT_DOUBLE_CLICK);
 
@@ -932,55 +938,67 @@
 
     @Test
     public void vibrate_withIntensitySettings_appliesSettingsToScaleVibrations() throws Exception {
-        mVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
+        int defaultNotificationIntensity =
+                mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_NOTIFICATION);
         setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_HIGH);
+                defaultNotificationIntensity < Vibrator.VIBRATION_INTENSITY_HIGH
+                        ? defaultNotificationIntensity + 1
+                        : defaultNotificationIntensity);
+
+        int defaultTouchIntensity =
+                mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_TOUCH);
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
+                defaultTouchIntensity > Vibrator.VIBRATION_INTENSITY_LOW
+                        ? defaultTouchIntensity - 1
+                        : defaultTouchIntensity);
+
+        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
+                mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_ALARM));
+        setUserSetting(Settings.System.MEDIA_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_HIGH);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_OFF);
 
         mockVibrators(1);
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
         fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
                 IVibrator.CAP_COMPOSE_EFFECTS);
-        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
         VibratorManagerService service = createSystemReadyService();
 
         vibrate(service, CombinedVibration.startSequential()
-                .addNext(1, VibrationEffect.createOneShot(20, 100))
+                .addNext(1, VibrationEffect.createOneShot(100, 125))
                 .combine(), NOTIFICATION_ATTRS);
         assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 1,
                 service, TEST_TIMEOUT_MILLIS));
 
         vibrate(service, VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
                 .compose(), HAPTIC_FEEDBACK_ATTRS);
+        assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 2,
+                service, TEST_TIMEOUT_MILLIS));
+
+        vibrate(service, VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+                .compose(), ALARM_ATTRS);
         assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 3,
                 service, TEST_TIMEOUT_MILLIS));
 
-        vibrate(service, CombinedVibration.startParallel()
-                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
-                .combine(), ALARM_ATTRS);
-        assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 4,
+        vibrate(service, VibrationEffect.createOneShot(100, 125), RINGTONE_ATTRS);
+        assertFalse(waitUntil(s -> fakeVibrator.getEffectSegments().size() > 3,
                 service, TEST_TIMEOUT_MILLIS));
 
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
+        assertEquals(3, fakeVibrator.getEffectSegments().size());
 
-        assertEquals(4, fakeVibrator.getEffectSegments().size());
+        // Notification vibrations will be scaled with SCALE_HIGH or none if default is high.
+        assertEquals(defaultNotificationIntensity < Vibrator.VIBRATION_INTENSITY_HIGH,
+                0.6 < fakeVibrator.getAmplitudes().get(0));
 
-        // Notification vibrations will be scaled with SCALE_VERY_HIGH.
-        assertTrue(0.6 < fakeVibrator.getAmplitudes().get(0));
+        // Haptic feedback vibrations will be scaled with SCALE_LOW or none if default is low.
+        assertEquals(defaultTouchIntensity > Vibrator.VIBRATION_INTENSITY_LOW,
+                0.5 > ((PrimitiveSegment) fakeVibrator.getEffectSegments().get(1)).getScale());
 
-        // Haptic feedback vibrations will be scaled with SCALE_LOW.
-        assertTrue(0.5 < ((PrimitiveSegment) fakeVibrator.getEffectSegments().get(1)).getScale());
-        assertTrue(0.5 > ((PrimitiveSegment) fakeVibrator.getEffectSegments().get(2)).getScale());
-
-        // Alarm vibration is always VIBRATION_INTENSITY_HIGH.
-        PrebakedSegment expected = new PrebakedSegment(
-                VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG);
-        assertEquals(expected, fakeVibrator.getEffectSegments().get(3));
+        // Alarm vibration will be scaled with SCALE_NONE.
+        assertEquals(1f,
+                ((PrimitiveSegment) fakeVibrator.getEffectSegments().get(2)).getScale(), 1e-5);
 
         // Ring vibrations have intensity OFF and are not played.
     }
@@ -1100,7 +1118,7 @@
         int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
         mExternalVibratorService.onExternalVibrationStop(externalVibration);
 
-        assertEquals(IExternalVibratorService.SCALE_NONE, scale);
+        assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
         assertEquals(Arrays.asList(false, true, false),
                 mVibratorProviders.get(1).getExternalControlStates());
     }
@@ -1127,8 +1145,8 @@
                 ringtoneAudioAttrs, secondController);
         int secondScale = mExternalVibratorService.onExternalVibrationStart(secondVibration);
 
-        assertEquals(IExternalVibratorService.SCALE_NONE, firstScale);
-        assertEquals(IExternalVibratorService.SCALE_NONE, secondScale);
+        assertNotEquals(IExternalVibratorService.SCALE_MUTE, firstScale);
+        assertNotEquals(IExternalVibratorService.SCALE_MUTE, secondScale);
         verify(firstController).mute();
         verify(secondController, never()).mute();
         // Set external control called only once.
@@ -1151,7 +1169,7 @@
         ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME, AUDIO_ATTRS,
                 mock(IExternalVibrationController.class));
         int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
-        assertEquals(IExternalVibratorService.SCALE_NONE, scale);
+        assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
 
         // Vibration is cancelled.
         assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
@@ -1163,7 +1181,6 @@
     public void onExternalVibration_withRingtone_usesRingerModeSettings() {
         mockVibrators(1);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
-        mVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM);
         AudioAttributes audioAttrs = new AudioAttributes.Builder()
                 .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
                 .build();
@@ -1181,13 +1198,13 @@
         setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 1);
         createSystemReadyService();
         scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
-        assertEquals(IExternalVibratorService.SCALE_NONE, scale);
+        assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
 
         setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
         setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
         createSystemReadyService();
         scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
-        assertEquals(IExternalVibratorService.SCALE_NONE, scale);
+        assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
     }
 
     private VibrationEffectSegment expectedPrebaked(int effectId) {
@@ -1235,10 +1252,6 @@
                 mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
     }
 
-    private void setGlobalSetting(String settingName, int value) {
-        Settings.Global.putInt(mContextSpy.getContentResolver(), settingName, value);
-    }
-
     private void vibrate(VibratorManagerService service, VibrationEffect effect,
             VibrationAttributes attrs) {
         vibrate(service, CombinedVibration.createParallel(effect), attrs);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 11777ef..d831903 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -182,7 +182,6 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
-import android.util.Slog;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
 import android.util.Xml;
@@ -7174,6 +7173,14 @@
     }
 
     @Test
+    public void testAreNotificationsEnabledForPackage_viaInternalService() throws Exception {
+        assertEquals(mInternalService.areNotificationsEnabledForPackage(
+                mContext.getPackageName(), mUid),
+                mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(), mUid));
+        verify(mPermissionHelper, never()).hasPermission(anyInt());
+    }
+
+    @Test
     public void testAreBubblesAllowedForPackage_crossUser() throws Exception {
         try {
             mBinderService.getBubblePreferenceForPackage(mContext.getPackageName(),
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
index 0c8fe35..1362628 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
@@ -536,6 +536,12 @@
     }
 
     @Test
+    public void testAreNotificationsEnabledForPackage_viaInternalService() {
+        mInternalService.areNotificationsEnabledForPackage(mContext.getPackageName(), mUid);
+        verify(mPermissionHelper).hasPermission(mUid);
+    }
+
+    @Test
     public void testGetPackageImportance() throws Exception {
         when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
         assertThat(mBinderService.getPackageImportance(mContext.getPackageName()))
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
index c77a474..f135d16 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
@@ -40,7 +40,10 @@
 @RunWith(AndroidJUnit4.class)
 public class VibratorHelperTest extends UiServiceTestCase {
 
+    // OFF/ON vibration pattern
     private static final long[] CUSTOM_PATTERN = new long[] { 100, 200, 300, 400 };
+    // (amplitude, frequency, duration) triples list
+    private static final float[] PWLE_PATTERN = new float[] { 1, 120, 100 };
 
     @Mock private Vibrator mVibrator;
 
@@ -58,12 +61,16 @@
     public void createWaveformVibration_insistent_createsRepeatingVibration() {
         assertRepeatingVibration(
                 VibratorHelper.createWaveformVibration(CUSTOM_PATTERN, /* insistent= */ true));
+        assertRepeatingVibration(
+                VibratorHelper.createPwleWaveformVibration(PWLE_PATTERN, /* insistent= */ true));
     }
 
     @Test
     public void createWaveformVibration_nonInsistent_createsSingleShotVibration() {
         assertSingleVibration(
                 VibratorHelper.createWaveformVibration(CUSTOM_PATTERN, /* insistent= */ false));
+        assertSingleVibration(
+                VibratorHelper.createPwleWaveformVibration(PWLE_PATTERN, /* insistent= */ false));
     }
 
     @Test
@@ -71,6 +78,11 @@
         assertNull(VibratorHelper.createWaveformVibration(null, false));
         assertNull(VibratorHelper.createWaveformVibration(new long[0], false));
         assertNull(VibratorHelper.createWaveformVibration(new long[] { 0, 0 }, false));
+
+        assertNull(VibratorHelper.createPwleWaveformVibration(null, false));
+        assertNull(VibratorHelper.createPwleWaveformVibration(new float[0], false));
+        assertNull(VibratorHelper.createPwleWaveformVibration(new float[] { 0 }, false));
+        assertNull(VibratorHelper.createPwleWaveformVibration(new float[] { 0, 0, 0 }, false));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
index 525888d..22e411e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
@@ -30,7 +31,6 @@
 import static android.window.DisplayAreaOrganizer.FEATURE_FULLSCREEN_MAGNIFICATION;
 import static android.window.DisplayAreaOrganizer.FEATURE_IME_PLACEHOLDER;
 import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED;
-import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL;
 import static android.window.DisplayAreaOrganizer.FEATURE_ROOT;
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_LAST;
@@ -49,12 +49,14 @@
 
 import static java.util.stream.Collectors.toList;
 
+import android.app.ActivityOptions;
 import android.content.res.Resources;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
 import android.view.SurfaceControl;
+import android.window.WindowContainerToken;
 
 import com.google.android.collect.Lists;
 
@@ -81,6 +83,8 @@
  */
 @Presubmit
 public class DisplayAreaPolicyBuilderTest {
+    private static final String KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID =
+            "android.test.launchTaskDisplayAreaFeatureId";
 
     @Rule
     public final SystemServicesTestRule mSystemServices = new SystemServicesTestRule();
@@ -104,6 +108,7 @@
         mImeContainer = new DisplayArea.Tokens(mWms, ABOVE_TASKS, "ImeContainer");
         mDisplayContent = mock(DisplayContent.class);
         doReturn(true).when(mDisplayContent).isTrusted();
+        doReturn(DEFAULT_DISPLAY).when(mDisplayContent).getDisplayId();
         mDisplayContent.isDefaultDisplay = true;
         mDefaultTaskDisplayArea = new TaskDisplayArea(mDisplayContent, mWms, "Tasks",
                 FEATURE_DEFAULT_TASK_CONTAINER);
@@ -197,25 +202,6 @@
     }
 
     @Test
-    public void testBuilder_defaultPolicy_hasOneHandedBackgroundFeature() {
-        final DisplayAreaPolicy.Provider defaultProvider = DisplayAreaPolicy.Provider.fromResources(
-                resourcesWithProvider(""));
-        final DisplayAreaPolicyBuilder.Result defaultPolicy =
-                (DisplayAreaPolicyBuilder.Result) defaultProvider.instantiate(mWms, mDisplayContent,
-                        mRoot, mImeContainer);
-        if (mDisplayContent.isDefaultDisplay) {
-            final List<Feature> features = defaultPolicy.getFeatures();
-            boolean hasOneHandedBackgroundFeature = false;
-            for (Feature feature : features) {
-                hasOneHandedBackgroundFeature |=
-                        feature.getId() == FEATURE_ONE_HANDED_BACKGROUND_PANEL;
-            }
-
-            assertThat(hasOneHandedBackgroundFeature).isTrue();
-        }
-    }
-
-    @Test
     public void testBuilder_defaultPolicy_hasWindowedMagnificationFeature() {
         final DisplayAreaPolicy.Provider defaultProvider = DisplayAreaPolicy.Provider.fromResources(
                 resourcesWithProvider(""));
@@ -731,6 +717,208 @@
                 .build();
     }
 
+    @Test
+    public void testGetTaskDisplayArea_DefaultFunction_NullOptions_ReturnsDefaultTda() {
+        final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy0 =
+                new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
+                        .setTaskDisplayAreas(mTaskDisplayAreaList);
+        final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy1 =
+                new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot1)
+                        .setImeContainer(mImeContainer)
+                        .setTaskDisplayAreas(Lists.newArrayList(mTda1));
+        final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy2 =
+                new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot2)
+                        .setTaskDisplayAreas(Lists.newArrayList(mTda2));
+        final DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder()
+                .setRootHierarchy(hierarchy0)
+                .addDisplayAreaGroupHierarchy(hierarchy1)
+                .addDisplayAreaGroupHierarchy(hierarchy2)
+                .build(mWms);
+
+        final TaskDisplayArea tda = policy.getTaskDisplayArea(null /* options */);
+
+        assertThat(tda).isEqualTo(mDefaultTaskDisplayArea);
+        assertThat(tda).isEqualTo(policy.getDefaultTaskDisplayArea());
+    }
+
+    @Test
+    public void testGetTaskDisplayArea_DefaultFunction_NotContainsLunchedTda_ReturnsDefaultTda() {
+        final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy0 =
+                new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
+                        .setTaskDisplayAreas(mTaskDisplayAreaList);
+        final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy1 =
+                new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot1)
+                        .setImeContainer(mImeContainer)
+                        .setTaskDisplayAreas(Lists.newArrayList(mTda1));
+        final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy2 =
+                new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot2)
+                        .setTaskDisplayAreas(Lists.newArrayList(mTda2));
+        final DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder()
+                .setRootHierarchy(hierarchy0)
+                .addDisplayAreaGroupHierarchy(hierarchy1)
+                .addDisplayAreaGroupHierarchy(hierarchy2)
+                .build(mWms);
+
+        final TaskDisplayArea tda = policy.getTaskDisplayArea(new Bundle());
+
+        assertThat(tda).isEqualTo(mDefaultTaskDisplayArea);
+        assertThat(tda).isEqualTo(policy.getDefaultTaskDisplayArea());
+    }
+
+    @Test
+    public void testGetTaskDisplayArea_DefaultFunction_InvalidTdaToken_ReturnsDefaultTda() {
+        final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy0 =
+                new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
+                        .setTaskDisplayAreas(mTaskDisplayAreaList);
+        final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy1 =
+                new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot1)
+                        .setImeContainer(mImeContainer)
+                        .setTaskDisplayAreas(Lists.newArrayList(mTda1));
+        final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy2 =
+                new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot2)
+                        .setTaskDisplayAreas(Lists.newArrayList(mTda2));
+        final DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder()
+                .setRootHierarchy(hierarchy0)
+                .addDisplayAreaGroupHierarchy(hierarchy1)
+                .addDisplayAreaGroupHierarchy(hierarchy2)
+                .build(mWms);
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        final WindowContainerToken fakeToken = mRoot.mRemoteToken.toWindowContainerToken();
+        options.setLaunchTaskDisplayArea(fakeToken);
+
+        final TaskDisplayArea tda = policy.getTaskDisplayArea(options.toBundle());
+
+        assertThat(tda).isEqualTo(mDefaultTaskDisplayArea);
+        assertThat(tda).isEqualTo(policy.getDefaultTaskDisplayArea());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetTaskDisplayArea_DefaultFunction_TdaOnDifferentDisplay_ThrowException() {
+        final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy0 =
+                new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
+                        .setTaskDisplayAreas(mTaskDisplayAreaList);
+        final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy1 =
+                new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot1)
+                        .setImeContainer(mImeContainer)
+                        .setTaskDisplayAreas(Lists.newArrayList(mTda1));
+        final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy2 =
+                new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot2)
+                        .setTaskDisplayAreas(Lists.newArrayList(mTda2));
+        final DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder()
+                .setRootHierarchy(hierarchy0)
+                .addDisplayAreaGroupHierarchy(hierarchy1)
+                .addDisplayAreaGroupHierarchy(hierarchy2)
+                .build(mWms);
+        final TaskDisplayArea tdaOnSecondaryDisplay = mock(TaskDisplayArea.class);
+        doReturn(DEFAULT_DISPLAY + 1).when(tdaOnSecondaryDisplay).getDisplayId();
+        doReturn(tdaOnSecondaryDisplay).when(tdaOnSecondaryDisplay).asTaskDisplayArea();
+        tdaOnSecondaryDisplay.mRemoteToken = new WindowContainer.RemoteToken(tdaOnSecondaryDisplay);
+
+        final WindowContainerToken tdaToken = tdaOnSecondaryDisplay.mRemoteToken
+                .toWindowContainerToken();
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchTaskDisplayArea(tdaToken);
+        final TaskDisplayArea tda = policy.getTaskDisplayArea(options.toBundle());
+
+        assertThat(tda).isEqualTo(mDefaultTaskDisplayArea);
+        assertThat(tda).isEqualTo(policy.getDefaultTaskDisplayArea());
+    }
+
+    @Test
+    public void testGetTaskDisplayArea_DefaultFunction_ContainsTdaToken_ReturnsTda() {
+        final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy0 =
+                new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
+                        .setTaskDisplayAreas(mTaskDisplayAreaList);
+        final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy1 =
+                new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot1)
+                        .setImeContainer(mImeContainer)
+                        .setTaskDisplayAreas(Lists.newArrayList(mTda1));
+        final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy2 =
+                new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot2)
+                        .setTaskDisplayAreas(Lists.newArrayList(mTda2));
+        final DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder()
+                .setRootHierarchy(hierarchy0)
+                .addDisplayAreaGroupHierarchy(hierarchy1)
+                .addDisplayAreaGroupHierarchy(hierarchy2)
+                .build(mWms);
+        final ActivityOptions options = ActivityOptions.makeBasic();
+
+        final WindowContainerToken defaultTdaToken = mDefaultTaskDisplayArea.mRemoteToken
+                .toWindowContainerToken();
+        options.setLaunchTaskDisplayArea(defaultTdaToken);
+        TaskDisplayArea tda = policy.getTaskDisplayArea(options.toBundle());
+
+        assertThat(tda).isEqualTo(mDefaultTaskDisplayArea);
+        assertThat(tda).isEqualTo(policy.getDefaultTaskDisplayArea());
+
+        final WindowContainerToken tda1Token = mTda1.mRemoteToken.toWindowContainerToken();
+        options.setLaunchTaskDisplayArea(tda1Token);
+        tda = policy.getTaskDisplayArea(options.toBundle());
+
+        assertThat(tda).isEqualTo(mTda1);
+
+        final WindowContainerToken tda2Token = mTda2.mRemoteToken.toWindowContainerToken();
+        options.setLaunchTaskDisplayArea(tda2Token);
+        tda = policy.getTaskDisplayArea(options.toBundle());
+
+        assertThat(tda).isEqualTo(mTda2);
+    }
+
+    @Test
+    public void testBuilder_getTaskDisplayArea_setSelectTaskDisplayAreaFunc() {
+        final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy0 =
+                new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
+                        .setTaskDisplayAreas(mTaskDisplayAreaList);
+        final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy1 =
+                new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot1)
+                        .setImeContainer(mImeContainer)
+                        .setTaskDisplayAreas(Lists.newArrayList(mTda1));
+        final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy2 =
+                new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot2)
+                        .setTaskDisplayAreas(Lists.newArrayList(mTda2));
+        final DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder()
+                .setRootHierarchy(hierarchy0)
+                .setSelectTaskDisplayAreaFunc((options) -> {
+                    if (options == null) {
+                        return mDefaultTaskDisplayArea;
+                    }
+                    final int tdaFeatureId =
+                            options.getInt(KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID);
+                    if (tdaFeatureId == mTda1.mFeatureId) {
+                        return mTda1;
+                    }
+                    if (tdaFeatureId == mTda2.mFeatureId) {
+                        return mTda2;
+                    }
+                    return mDefaultTaskDisplayArea;
+                })
+                .addDisplayAreaGroupHierarchy(hierarchy1)
+                .addDisplayAreaGroupHierarchy(hierarchy2)
+                .build(mWms);
+
+        TaskDisplayArea tda = policy.getTaskDisplayArea(null /* options */);
+
+        assertThat(tda).isEqualTo(mDefaultTaskDisplayArea);
+        assertThat(tda).isEqualTo(policy.getDefaultTaskDisplayArea());
+
+        final Bundle options = new Bundle();
+        options.putInt(KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID, -1);
+        tda = policy.getTaskDisplayArea(options);
+        assertThat(tda).isEqualTo(mDefaultTaskDisplayArea);
+
+        options.putInt(KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID, mDefaultTaskDisplayArea.mFeatureId);
+        tda = policy.getTaskDisplayArea(options);
+        assertThat(tda).isEqualTo(mDefaultTaskDisplayArea);
+
+        options.putInt(KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID, mTda1.mFeatureId);
+        tda = policy.getTaskDisplayArea(options);
+        assertThat(tda).isEqualTo(mTda1);
+
+        options.putInt(KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID, mTda2.mFeatureId);
+        tda = policy.getTaskDisplayArea(options);
+        assertThat(tda).isEqualTo(mTda2);
+    }
+
     private static Resources resourcesWithProvider(String provider) {
         Resources mock = mock(Resources.class);
         when(mock.getString(
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 2ef59f6..2f78b58 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1718,6 +1718,7 @@
     @Test
     public void testShellTransitRotation() {
         DisplayContent dc = createNewDisplay();
+        dc.setLastHasContent();
 
         final TestTransitionPlayer testPlayer = registerTestTransitionPlayer();
         final DisplayRotation dr = dc.getDisplayRotation();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index ed3888c..141588a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -488,6 +488,7 @@
         final TestTransitionPlayer player = registerTestTransitionPlayer();
 
         mDisplayContent.getDisplayRotation().setRotation(mDisplayContent.getRotation() + 1);
+        mDisplayContent.setLastHasContent();
         mDisplayContent.requestChangeTransitionIfNeeded(1 /* any changes */,
                 null /* displayChange */);
         final FadeRotationAnimationController fadeController =
@@ -536,6 +537,7 @@
                 null /* remoteTransition */, null /* displayChange */);
         mDisplayContent.getDisplayRotation().setRotation(mDisplayContent.getRotation() + 1);
         final int anyChanges = 1;
+        mDisplayContent.setLastHasContent();
         mDisplayContent.requestChangeTransitionIfNeeded(anyChanges, null /* displayChange */);
         transition.setKnownConfigChanges(mDisplayContent, anyChanges);
         final FadeRotationAnimationController fadeController =
@@ -550,9 +552,11 @@
         assertTrue(app.getTask().inTransition());
 
         final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class);
+        final SurfaceControl leash = statusBar.mToken.getAnimationLeash();
+        doReturn(true).when(leash).isValid();
         player.onTransactionReady(startTransaction);
         // The leash should be unrotated.
-        verify(startTransaction).setMatrix(eq(statusBar.mToken.getAnimationLeash()), any(), any());
+        verify(startTransaction).setMatrix(eq(leash), any(), any());
 
         // The redrawn window will be faded in when the transition finishes. And because this test
         // only use one non-activity window, the fade rotation controller should also be cleared.
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index d94fafc..ce9530c 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -701,8 +701,15 @@
          */
         public static final int PROPERTY_CROSS_SIM = 0x00004000;
 
+        /**
+         * Connection is a tethered external call.
+         * Indicates that the {@link Connection} is fixed on this device but the audio streams are
+         * re-routed to another device.
+         */
+        public static final int PROPERTY_TETHERED_CALL = 0x00008000;
+
         //******************************************************************************************
-        // Next PROPERTY value: 0x00004000
+        // Next PROPERTY value: 0x00010000
         //******************************************************************************************
 
         private final @CallState int mState;
@@ -899,6 +906,9 @@
             if (hasProperty(properties, PROPERTY_CROSS_SIM)) {
                 builder.append(" PROPERTY_CROSS_SIM");
             }
+            if (hasProperty(properties, PROPERTY_TETHERED_CALL)) {
+                builder.append(" PROPERTY_TETHERED_CALL");
+            }
             builder.append("]");
             return builder.toString();
         }
diff --git a/telecomm/java/android/telecom/CallAudioState.java b/telecomm/java/android/telecom/CallAudioState.java
index fccdf76..55957bd 100644
--- a/telecomm/java/android/telecom/CallAudioState.java
+++ b/telecomm/java/android/telecom/CallAudioState.java
@@ -27,7 +27,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -42,7 +41,8 @@
 public final class CallAudioState implements Parcelable {
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(value={ROUTE_EARPIECE, ROUTE_BLUETOOTH, ROUTE_WIRED_HEADSET, ROUTE_SPEAKER},
+    @IntDef(value = {ROUTE_EARPIECE, ROUTE_BLUETOOTH, ROUTE_WIRED_HEADSET, ROUTE_SPEAKER,
+            ROUTE_EXTERNAL},
             flag=true)
     public @interface CallAudioRoute {}
 
@@ -58,6 +58,9 @@
     /** Direct the audio stream through the device's speakerphone. */
     public static final int ROUTE_SPEAKER       = 0x00000008;
 
+    /** Direct the audio stream through another device. */
+    public static final int ROUTE_EXTERNAL       = 0x00000010;
+
     /**
      * Direct the audio stream through the device's earpiece or wired headset if one is
      * connected.
@@ -70,7 +73,7 @@
      * @hide
      **/
     public static final int ROUTE_ALL = ROUTE_EARPIECE | ROUTE_BLUETOOTH | ROUTE_WIRED_HEADSET |
-            ROUTE_SPEAKER;
+            ROUTE_SPEAKER | ROUTE_EXTERNAL;
 
     private final boolean isMuted;
     private final int route;
@@ -189,7 +192,11 @@
      */
     @CallAudioRoute
     public int getSupportedRouteMask() {
-        return supportedRouteMask;
+        if (route == ROUTE_EXTERNAL) {
+            return ROUTE_EXTERNAL;
+        } else {
+            return supportedRouteMask;
+        }
     }
 
     /**
@@ -233,6 +240,10 @@
             listAppend(buffer, "SPEAKER");
         }
 
+        if ((route & ROUTE_EXTERNAL) == ROUTE_EXTERNAL) {
+            listAppend(buffer, "EXTERNAL");
+        }
+
         return buffer.toString();
     }
 
@@ -248,10 +259,10 @@
             int route = source.readInt();
             int supportedRouteMask = source.readInt();
             BluetoothDevice activeBluetoothDevice = source.readParcelable(
-                    ClassLoader.getSystemClassLoader(), android.bluetooth.BluetoothDevice.class);
+                    ClassLoader.getSystemClassLoader());
             List<BluetoothDevice> supportedBluetoothDevices = new ArrayList<>();
             source.readParcelableList(supportedBluetoothDevices,
-                    ClassLoader.getSystemClassLoader(), android.bluetooth.BluetoothDevice.class);
+                    ClassLoader.getSystemClassLoader());
             return new CallAudioState(isMuted, route,
                     supportedRouteMask, activeBluetoothDevice, supportedBluetoothDevices);
         }
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 21a1804..d63cdc0 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -561,6 +561,15 @@
      */
     public static final int PROPERTY_CROSS_SIM = 1 << 13;
 
+    /**
+     * Connection is a tethered external call.
+     * <p>
+     * Indicates that the {@link Connection} is fixed on this device but the audio streams are
+     * re-routed to another device.
+     * <p>
+     */
+    public static final int PROPERTY_TETHERED_CALL = 1 << 14;
+
     //**********************************************************************************************
     // Next PROPERTY value: 1<<14
     //**********************************************************************************************
@@ -3537,9 +3546,9 @@
             mIsBlocked = in.readByte() != 0;
             mIsInContacts = in.readByte() != 0;
             CallScreeningService.ParcelableCallResponse response
-                    = in.readParcelable(CallScreeningService.class.getClassLoader(), android.telecom.CallScreeningService.ParcelableCallResponse.class);
+                    = in.readParcelable(CallScreeningService.class.getClassLoader());
             mCallResponse = response == null ? null : response.toCallResponse();
-            mCallScreeningComponent = in.readParcelable(ComponentName.class.getClassLoader(), android.content.ComponentName.class);
+            mCallScreeningComponent = in.readParcelable(ComponentName.class.getClassLoader());
         }
 
         @NonNull
diff --git a/telecomm/java/android/telecom/ConnectionRequest.java b/telecomm/java/android/telecom/ConnectionRequest.java
index 1172e13..be5fae4 100644
--- a/telecomm/java/android/telecom/ConnectionRequest.java
+++ b/telecomm/java/android/telecom/ConnectionRequest.java
@@ -272,17 +272,17 @@
     }
 
     private ConnectionRequest(Parcel in) {
-        mAccountHandle = in.readParcelable(getClass().getClassLoader(), android.telecom.PhoneAccountHandle.class);
-        mAddress = in.readParcelable(getClass().getClassLoader(), android.net.Uri.class);
-        mExtras = in.readParcelable(getClass().getClassLoader(), android.os.Bundle.class);
+        mAccountHandle = in.readParcelable(getClass().getClassLoader());
+        mAddress = in.readParcelable(getClass().getClassLoader());
+        mExtras = in.readParcelable(getClass().getClassLoader());
         mVideoState = in.readInt();
         mTelecomCallId = in.readString();
         mShouldShowIncomingCallUi = in.readInt() == 1;
-        mRttPipeFromInCall = in.readParcelable(getClass().getClassLoader(), android.os.ParcelFileDescriptor.class);
-        mRttPipeToInCall = in.readParcelable(getClass().getClassLoader(), android.os.ParcelFileDescriptor.class);
+        mRttPipeFromInCall = in.readParcelable(getClass().getClassLoader());
+        mRttPipeToInCall = in.readParcelable(getClass().getClassLoader());
 
         mParticipants = new ArrayList<Uri>();
-        in.readList(mParticipants, getClass().getClassLoader(), android.net.Uri.class);
+        in.readList(mParticipants, getClass().getClassLoader());
 
         mIsAdhocConference = in.readInt() == 1;
     }
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index 0f034ad..ed7b79f 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -287,7 +287,7 @@
             int tone = source.readInt();
             int telephonyDisconnectCause = source.readInt();
             int telephonyPreciseDisconnectCause = source.readInt();
-            ImsReasonInfo imsReasonInfo = source.readParcelable(null, android.telephony.ims.ImsReasonInfo.class);
+            ImsReasonInfo imsReasonInfo = source.readParcelable(null);
             return new DisconnectCause(code, label, description, reason, tone,
                     telephonyDisconnectCause, telephonyPreciseDisconnectCause, imsReasonInfo);
         }
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index f412a18..320308c 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -623,9 +623,9 @@
             ClassLoader classLoader = ParcelableCall.class.getClassLoader();
             String id = source.readString();
             int state = source.readInt();
-            DisconnectCause disconnectCause = source.readParcelable(classLoader, android.telecom.DisconnectCause.class);
+            DisconnectCause disconnectCause = source.readParcelable(classLoader);
             List<String> cannedSmsResponses = new ArrayList<>();
-            source.readList(cannedSmsResponses, classLoader, java.lang.String.class);
+            source.readList(cannedSmsResponses, classLoader);
             int capabilities = source.readInt();
             int properties = source.readInt();
             long connectTimeMillis = source.readLong();
@@ -633,23 +633,23 @@
             int handlePresentation = source.readInt();
             String callerDisplayName = source.readString();
             int callerDisplayNamePresentation = source.readInt();
-            GatewayInfo gatewayInfo = source.readParcelable(classLoader, android.telecom.GatewayInfo.class);
-            PhoneAccountHandle accountHandle = source.readParcelable(classLoader, android.telecom.PhoneAccountHandle.class);
+            GatewayInfo gatewayInfo = source.readParcelable(classLoader);
+            PhoneAccountHandle accountHandle = source.readParcelable(classLoader);
             boolean isVideoCallProviderChanged = source.readByte() == 1;
             IVideoProvider videoCallProvider =
                     IVideoProvider.Stub.asInterface(source.readStrongBinder());
             String parentCallId = source.readString();
             List<String> childCallIds = new ArrayList<>();
-            source.readList(childCallIds, classLoader, java.lang.String.class);
-            StatusHints statusHints = source.readParcelable(classLoader, android.telecom.StatusHints.class);
+            source.readList(childCallIds, classLoader);
+            StatusHints statusHints = source.readParcelable(classLoader);
             int videoState = source.readInt();
             List<String> conferenceableCallIds = new ArrayList<>();
-            source.readList(conferenceableCallIds, classLoader, java.lang.String.class);
+            source.readList(conferenceableCallIds, classLoader);
             Bundle intentExtras = source.readBundle(classLoader);
             Bundle extras = source.readBundle(classLoader);
             int supportedAudioRoutes = source.readInt();
             boolean isRttCallChanged = source.readByte() == 1;
-            ParcelableRttCall rttCall = source.readParcelable(classLoader, android.telecom.ParcelableRttCall.class);
+            ParcelableRttCall rttCall = source.readParcelable(classLoader);
             long creationTimeMillis = source.readLong();
             int callDirection = source.readInt();
             int callerNumberVerificationStatus = source.readInt();
diff --git a/telecomm/java/android/telecom/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java
index e57c833..1f8aafb 100644
--- a/telecomm/java/android/telecom/ParcelableConference.java
+++ b/telecomm/java/android/telecom/ParcelableConference.java
@@ -292,24 +292,24 @@
         @Override
         public ParcelableConference createFromParcel(Parcel source) {
             ClassLoader classLoader = ParcelableConference.class.getClassLoader();
-            PhoneAccountHandle phoneAccount = source.readParcelable(classLoader, android.telecom.PhoneAccountHandle.class);
+            PhoneAccountHandle phoneAccount = source.readParcelable(classLoader);
             int state = source.readInt();
             int capabilities = source.readInt();
             List<String> connectionIds = new ArrayList<>(2);
-            source.readList(connectionIds, classLoader, java.lang.String.class);
+            source.readList(connectionIds, classLoader);
             long connectTimeMillis = source.readLong();
             IVideoProvider videoCallProvider =
                     IVideoProvider.Stub.asInterface(source.readStrongBinder());
             int videoState = source.readInt();
-            StatusHints statusHints = source.readParcelable(classLoader, android.telecom.StatusHints.class);
+            StatusHints statusHints = source.readParcelable(classLoader);
             Bundle extras = source.readBundle(classLoader);
             int properties = source.readInt();
             long connectElapsedTimeMillis = source.readLong();
-            Uri address = source.readParcelable(classLoader, android.net.Uri.class);
+            Uri address = source.readParcelable(classLoader);
             int addressPresentation = source.readInt();
             String callerDisplayName = source.readString();
             int callerDisplayNamePresentation = source.readInt();
-            DisconnectCause disconnectCause = source.readParcelable(classLoader, android.telecom.DisconnectCause.class);
+            DisconnectCause disconnectCause = source.readParcelable(classLoader);
             boolean isRingbackRequested = source.readInt() == 1;
             int callDirection = source.readInt();
 
diff --git a/telecomm/java/android/telecom/ParcelableConnection.java b/telecomm/java/android/telecom/ParcelableConnection.java
index 7b83338..2b9ce9b 100644
--- a/telecomm/java/android/telecom/ParcelableConnection.java
+++ b/telecomm/java/android/telecom/ParcelableConnection.java
@@ -261,10 +261,10 @@
         public ParcelableConnection createFromParcel(Parcel source) {
             ClassLoader classLoader = ParcelableConnection.class.getClassLoader();
 
-            PhoneAccountHandle phoneAccount = source.readParcelable(classLoader, android.telecom.PhoneAccountHandle.class);
+            PhoneAccountHandle phoneAccount = source.readParcelable(classLoader);
             int state = source.readInt();
             int capabilities = source.readInt();
-            Uri address = source.readParcelable(classLoader, android.net.Uri.class);
+            Uri address = source.readParcelable(classLoader);
             int addressPresentation = source.readInt();
             String callerDisplayName = source.readString();
             int callerDisplayNamePresentation = source.readInt();
@@ -274,8 +274,8 @@
             boolean ringbackRequested = source.readByte() == 1;
             boolean audioModeIsVoip = source.readByte() == 1;
             long connectTimeMillis = source.readLong();
-            StatusHints statusHints = source.readParcelable(classLoader, android.telecom.StatusHints.class);
-            DisconnectCause disconnectCause = source.readParcelable(classLoader, android.telecom.DisconnectCause.class);
+            StatusHints statusHints = source.readParcelable(classLoader);
+            DisconnectCause disconnectCause = source.readParcelable(classLoader);
             List<String> conferenceableConnectionIds = new ArrayList<>();
             source.readStringList(conferenceableConnectionIds);
             Bundle extras = Bundle.setDefusable(source.readBundle(classLoader), true);
diff --git a/telecomm/java/android/telecom/ParcelableRttCall.java b/telecomm/java/android/telecom/ParcelableRttCall.java
index b88473a..fbcf486 100644
--- a/telecomm/java/android/telecom/ParcelableRttCall.java
+++ b/telecomm/java/android/telecom/ParcelableRttCall.java
@@ -46,8 +46,8 @@
 
     protected ParcelableRttCall(Parcel in) {
         mRttMode = in.readInt();
-        mTransmitStream = in.readParcelable(ParcelFileDescriptor.class.getClassLoader(), android.os.ParcelFileDescriptor.class);
-        mReceiveStream = in.readParcelable(ParcelFileDescriptor.class.getClassLoader(), android.os.ParcelFileDescriptor.class);
+        mTransmitStream = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
+        mReceiveStream = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
     }
 
     public static final @android.annotation.NonNull Creator<ParcelableRttCall> CREATOR = new Creator<ParcelableRttCall>() {
diff --git a/telecomm/java/android/telecom/PhoneAccountSuggestion.java b/telecomm/java/android/telecom/PhoneAccountSuggestion.java
index d9f89d5..2589d95 100644
--- a/telecomm/java/android/telecom/PhoneAccountSuggestion.java
+++ b/telecomm/java/android/telecom/PhoneAccountSuggestion.java
@@ -84,7 +84,7 @@
     }
 
     private PhoneAccountSuggestion(Parcel in) {
-        mHandle = in.readParcelable(PhoneAccountHandle.class.getClassLoader(), android.telecom.PhoneAccountHandle.class);
+        mHandle = in.readParcelable(PhoneAccountHandle.class.getClassLoader());
         mReason = in.readInt();
         mShouldAutoSelect = in.readByte() != 0;
     }
diff --git a/telecomm/java/android/telecom/StatusHints.java b/telecomm/java/android/telecom/StatusHints.java
index 2faecc2..762c93a 100644
--- a/telecomm/java/android/telecom/StatusHints.java
+++ b/telecomm/java/android/telecom/StatusHints.java
@@ -132,8 +132,8 @@
 
     private StatusHints(Parcel in) {
         mLabel = in.readCharSequence();
-        mIcon = in.readParcelable(getClass().getClassLoader(), android.graphics.drawable.Icon.class);
-        mExtras = in.readParcelable(getClass().getClassLoader(), android.os.Bundle.class);
+        mIcon = in.readParcelable(getClass().getClassLoader());
+        mExtras = in.readParcelable(getClass().getClassLoader());
     }
 
     @Override
diff --git a/telephony/java/android/telephony/AvailableNetworkInfo.java b/telephony/java/android/telephony/AvailableNetworkInfo.java
index 6d673fb..2b355ae 100644
--- a/telephony/java/android/telephony/AvailableNetworkInfo.java
+++ b/telephony/java/android/telephony/AvailableNetworkInfo.java
@@ -185,9 +185,9 @@
         mMccMncs = new ArrayList<>();
         in.readStringList(mMccMncs);
         mBands = new ArrayList<>();
-        in.readList(mBands, Integer.class.getClassLoader(), java.lang.Integer.class);
+        in.readList(mBands, Integer.class.getClassLoader());
         mRadioAccessSpecifiers = new ArrayList<>();
-        in.readList(mRadioAccessSpecifiers, RadioAccessSpecifier.class.getClassLoader(), android.telephony.RadioAccessSpecifier.class);
+        in.readList(mRadioAccessSpecifiers, RadioAccessSpecifier.class.getClassLoader());
     }
 
     public AvailableNetworkInfo(int subId, int priority, @NonNull List<String> mccMncs,
diff --git a/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java
index 29152f1..0aa4b58 100644
--- a/telephony/java/android/telephony/BarringInfo.java
+++ b/telephony/java/android/telephony/BarringInfo.java
@@ -294,8 +294,8 @@
 
     /** @hide */
     public BarringInfo(Parcel p) {
-        mCellIdentity = p.readParcelable(CellIdentity.class.getClassLoader(), android.telephony.CellIdentity.class);
-        mBarringServiceInfos = p.readSparseArray(BarringServiceInfo.class.getClassLoader(), android.telephony.BarringInfo.BarringServiceInfo.class);
+        mCellIdentity = p.readParcelable(CellIdentity.class.getClassLoader());
+        mBarringServiceInfos = p.readSparseArray(BarringServiceInfo.class.getClassLoader());
     }
 
     @Override
diff --git a/telephony/java/android/telephony/CallAttributes.java b/telephony/java/android/telephony/CallAttributes.java
index b7bef39..0c258f4 100644
--- a/telephony/java/android/telephony/CallAttributes.java
+++ b/telephony/java/android/telephony/CallAttributes.java
@@ -53,9 +53,9 @@
     }
 
     private CallAttributes(Parcel in) {
-        this.mPreciseCallState = in.readParcelable(PreciseCallState.class.getClassLoader(), android.telephony.PreciseCallState.class);
+        this.mPreciseCallState = in.readParcelable(PreciseCallState.class.getClassLoader());
         this.mNetworkType = in.readInt();
-        this.mCallQuality = in.readParcelable(CallQuality.class.getClassLoader(), android.telephony.CallQuality.class);
+        this.mCallQuality = in.readParcelable(CallQuality.class.getClassLoader());
     }
 
     // getters
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 808df50..6d8edc5 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2935,19 +2935,6 @@
             "signal_strength_nr_nsa_use_lte_as_primary_bool";
 
     /**
-     * String array of TCP buffer sizes per network type.
-     * The entries should be of the following form, with values in bytes:
-     * "network_name:read_min,read_default,read_max,write_min,write_default,write_max".
-     * For NR (5G), the following network names should be used:
-     * - NR_NSA: NR NSA, sub-6 frequencies
-     * - NR_NSA_MMWAVE: NR NSA, mmwave frequencies
-     * - NR_SA: NR SA, sub-6 frequencies
-     * - NR_SA_MMWAVE: NR SA, mmwave frequencies
-     * @hide
-     */
-    public static final String KEY_TCP_BUFFERS_STRING_ARRAY = "tcp_buffers_string_array";
-
-    /**
      * String array of default bandwidth values per network type.
      * The entries should be of form: "network_name:downlink,uplink", with values in Kbps.
      * For NR (5G), the following network names should be used:
@@ -7426,7 +7413,7 @@
         /**
          * A priority list of ePDG addresses to be used. Possible values are {@link
          * #EPDG_ADDRESS_STATIC}, {@link #EPDG_ADDRESS_PLMN}, {@link #EPDG_ADDRESS_PCO}, {@link
-         * #EPDG_ADDRESS_CELLULAR_LOC}
+         * #EPDG_ADDRESS_CELLULAR_LOC}, {@link #EPDG_ADDRESS_VISITED_COUNTRY}
          */
         public static final String KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY =
                 KEY_PREFIX + "epdg_address_priority_int_array";
@@ -7605,7 +7592,8 @@
             EPDG_ADDRESS_STATIC,
             EPDG_ADDRESS_PLMN,
             EPDG_ADDRESS_PCO,
-            EPDG_ADDRESS_CELLULAR_LOC
+            EPDG_ADDRESS_CELLULAR_LOC,
+            EPDG_ADDRESS_VISITED_COUNTRY
         })
         public @interface EpdgAddressType {}
 
@@ -7619,6 +7607,8 @@
         public static final int EPDG_ADDRESS_PCO = 2;
         /** Use cellular location to chose epdg server */
         public static final int EPDG_ADDRESS_CELLULAR_LOC = 3;
+        /* Use Visited Country FQDN rule*/
+        public static final int EPDG_ADDRESS_VISITED_COUNTRY = 4;
 
         /** @hide */
         @IntDef({ID_TYPE_FQDN, ID_TYPE_RFC822_ADDR, ID_TYPE_KEY_ID})
@@ -8570,28 +8560,6 @@
                 "iDEN:14,14", "LTE:30000,15000", "HSPA+:13000,3400", "GSM:24,24",
                 "TD_SCDMA:115,115", "LTE_CA:30000,15000", "NR_NSA:47000,18000",
                 "NR_NSA_MMWAVE:145000,60000", "NR_SA:145000,60000", "NR_SA_MMWAVE:145000,60000"});
-        sDefaults.putStringArray(KEY_TCP_BUFFERS_STRING_ARRAY, new String[]{
-                "GPRS:4092,8760,48000,4096,8760,48000", "EDGE:4093,26280,70800,4096,16384,70800",
-                "UMTS:58254,349525,1048576,58254,349525,1048576",
-                "CDMA:4094,87380,262144,4096,16384,262144",
-                "1xRTT:16384,32768,131072,4096,16384,102400",
-                "EvDo_0:4094,87380,262144,4096,16384,262144",
-                "EvDo_A:4094,87380,262144,4096,16384,262144",
-                "HSDPA:61167,367002,1101005,8738,52429,262114",
-                "HSUPA:40778,244668,734003,16777,100663,301990",
-                "HSPA:40778,244668,734003,16777,100663,301990",
-                "EvDo_B:4094,87380,262144,4096,16384,262144",
-                "eHRPD:131072,262144,1048576,4096,16384,524288",
-                "iDEN:4094,87380,262144,4096,16384,262144",
-                "LTE:524288,1048576,2097152,262144,524288,1048576",
-                "HSPA+:122334,734003,2202010,32040,192239,576717",
-                "GSM:4092,8760,48000,4096,8760,48000",
-                "TD_SCDMA:58254,349525,1048576,58254,349525,1048576",
-                "LTE_CA:4096,6291456,12582912,4096,1048576,2097152",
-                "NR_NSA:2097152,6291456,16777216,512000,2097152,8388608",
-                "NR_NSA_MMWAVE:2097152,6291456,16777216,512000,2097152,8388608",
-                "NR_SA:2097152,6291456,16777216,512000,2097152,8388608",
-                "NR_SA_MMWAVE:2097152,6291456,16777216,512000,2097152,8388608"});
         sDefaults.putBoolean(KEY_BANDWIDTH_NR_NSA_USE_LTE_VALUE_FOR_UPLINK_BOOL, false);
         sDefaults.putString(KEY_WCDMA_DEFAULT_SIGNAL_STRENGTH_MEASUREMENT_STRING, "rssi");
         sDefaults.putBoolean(KEY_CONFIG_SHOW_ORIG_DIAL_STRING_FOR_CDMA_BOOL, false);
diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java
index b4b8aee..4db00cf 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -379,7 +379,7 @@
         mBands = in.createIntArray();
         mBandwidth = in.readInt();
         mAdditionalPlmns = (ArraySet<String>) in.readArraySet(null);
-        mCsgInfo = in.readParcelable(null, android.telephony.ClosedSubscriberGroupInfo.class);
+        mCsgInfo = in.readParcelable(null);
 
         updateGlobalCellId();
         if (DBG) log(toString());
diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java
index 90e6295..13d9373 100644
--- a/telephony/java/android/telephony/CellIdentityTdscdma.java
+++ b/telephony/java/android/telephony/CellIdentityTdscdma.java
@@ -297,7 +297,7 @@
         mCpid = in.readInt();
         mUarfcn = in.readInt();
         mAdditionalPlmns = (ArraySet<String>) in.readArraySet(null);
-        mCsgInfo = in.readParcelable(null, android.telephony.ClosedSubscriberGroupInfo.class);
+        mCsgInfo = in.readParcelable(null);
 
         updateGlobalCellId();
         if (DBG) log(toString());
diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java
index 72282cd..9b463da 100644
--- a/telephony/java/android/telephony/CellIdentityWcdma.java
+++ b/telephony/java/android/telephony/CellIdentityWcdma.java
@@ -313,7 +313,7 @@
         mPsc = in.readInt();
         mUarfcn = in.readInt();
         mAdditionalPlmns = (ArraySet<String>) in.readArraySet(null);
-        mCsgInfo = in.readParcelable(null, android.telephony.ClosedSubscriberGroupInfo.class);
+        mCsgInfo = in.readParcelable(null);
 
         updateGlobalCellId();
         if (DBG) log(toString());
diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java
index f5ba3ab..cd22abd 100644
--- a/telephony/java/android/telephony/CellSignalStrengthNr.java
+++ b/telephony/java/android/telephony/CellSignalStrengthNr.java
@@ -326,7 +326,7 @@
         mCsiRsrq = in.readInt();
         mCsiSinr = in.readInt();
         mCsiCqiTableIndex = in.readInt();
-        mCsiCqiReport = in.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class);
+        mCsiCqiReport = in.readArrayList(Integer.class.getClassLoader());
         mSsRsrp = in.readInt();
         mSsRsrq = in.readInt();
         mSsSinr = in.readInt();
diff --git a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
index 837124f..957f683 100644
--- a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
+++ b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
@@ -105,7 +105,7 @@
         isDcNrRestricted = source.readBoolean();
         isNrAvailable = source.readBoolean();
         isEnDcAvailable = source.readBoolean();
-        mVopsSupportInfo = source.readParcelable(VopsSupportInfo.class.getClassLoader(), android.telephony.VopsSupportInfo.class);
+        mVopsSupportInfo = source.readParcelable(VopsSupportInfo.class.getClassLoader());
     }
 
     @Override
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index c18443e..6a80766 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -311,12 +311,12 @@
         mRejectCause = source.readInt();
         mEmergencyOnly = source.readBoolean();
         mAvailableServices = new ArrayList<>();
-        source.readList(mAvailableServices, Integer.class.getClassLoader(), java.lang.Integer.class);
-        mCellIdentity = source.readParcelable(CellIdentity.class.getClassLoader(), android.telephony.CellIdentity.class);
+        source.readList(mAvailableServices, Integer.class.getClassLoader());
+        mCellIdentity = source.readParcelable(CellIdentity.class.getClassLoader());
         mVoiceSpecificInfo = source.readParcelable(
-                VoiceSpecificRegistrationInfo.class.getClassLoader(), android.telephony.VoiceSpecificRegistrationInfo.class);
+                VoiceSpecificRegistrationInfo.class.getClassLoader());
         mDataSpecificInfo = source.readParcelable(
-                DataSpecificRegistrationInfo.class.getClassLoader(), android.telephony.DataSpecificRegistrationInfo.class);
+                DataSpecificRegistrationInfo.class.getClassLoader());
         mNrState = source.readInt();
         mRplmn = source.readString();
         mIsUsingCarrierAggregation = source.readBoolean();
diff --git a/telephony/java/android/telephony/NetworkScanRequest.java b/telephony/java/android/telephony/NetworkScanRequest.java
index c8b8ffb..326f417 100644
--- a/telephony/java/android/telephony/NetworkScanRequest.java
+++ b/telephony/java/android/telephony/NetworkScanRequest.java
@@ -221,7 +221,8 @@
 
     private NetworkScanRequest(Parcel in) {
         mScanType = in.readInt();
-        Parcelable[] tempSpecifiers = in.readParcelableArray(Object.class.getClassLoader());
+        Parcelable[] tempSpecifiers = in.readParcelableArray(Object.class.getClassLoader(),
+                RadioAccessSpecifier.class);
         if (tempSpecifiers != null) {
             mSpecifiers = new RadioAccessSpecifier[tempSpecifiers.length];
             for (int i = 0; i < tempSpecifiers.length; i++) {
diff --git a/telephony/java/android/telephony/PhoneCapability.java b/telephony/java/android/telephony/PhoneCapability.java
index 63e3468..a3aaf61 100644
--- a/telephony/java/android/telephony/PhoneCapability.java
+++ b/telephony/java/android/telephony/PhoneCapability.java
@@ -150,7 +150,7 @@
         mMaxActiveDataSubscriptions = in.readInt();
         mNetworkValidationBeforeSwitchSupported = in.readBoolean();
         mLogicalModemList = new ArrayList<>();
-        in.readList(mLogicalModemList, ModemInfo.class.getClassLoader(), android.telephony.ModemInfo.class);
+        in.readList(mLogicalModemList, ModemInfo.class.getClassLoader());
         mDeviceNrCapabilities = in.createIntArray();
     }
 
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index 2670b03..ce2f3f9 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -125,9 +125,9 @@
         mId = in.readInt();
         mState = in.readInt();
         mNetworkType = in.readInt();
-        mLinkProperties = in.readParcelable(LinkProperties.class.getClassLoader(), android.net.LinkProperties.class);
+        mLinkProperties = in.readParcelable(LinkProperties.class.getClassLoader());
         mFailCause = in.readInt();
-        mApnSetting = in.readParcelable(ApnSetting.class.getClassLoader(), android.telephony.data.ApnSetting.class);
+        mApnSetting = in.readParcelable(ApnSetting.class.getClassLoader());
     }
 
     /**
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 70da9b9..5affb62 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -479,7 +479,7 @@
         mIsEmergencyOnly = in.readInt() != 0;
         mArfcnRsrpBoost = in.readInt();
         synchronized (mNetworkRegistrationInfos) {
-            in.readList(mNetworkRegistrationInfos, NetworkRegistrationInfo.class.getClassLoader(), android.telephony.NetworkRegistrationInfo.class);
+            in.readList(mNetworkRegistrationInfos, NetworkRegistrationInfo.class.getClassLoader());
         }
         mChannelNumber = in.readInt();
         mCellBandwidths = in.createIntArray();
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index f74ef0f..b7bc467 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -275,12 +275,12 @@
     public SignalStrength(Parcel in) {
         if (DBG) log("Size of signalstrength parcel:" + in.dataSize());
 
-        mCdma = in.readParcelable(CellSignalStrengthCdma.class.getClassLoader(), android.telephony.CellSignalStrengthCdma.class);
-        mGsm = in.readParcelable(CellSignalStrengthGsm.class.getClassLoader(), android.telephony.CellSignalStrengthGsm.class);
-        mWcdma = in.readParcelable(CellSignalStrengthWcdma.class.getClassLoader(), android.telephony.CellSignalStrengthWcdma.class);
-        mTdscdma = in.readParcelable(CellSignalStrengthTdscdma.class.getClassLoader(), android.telephony.CellSignalStrengthTdscdma.class);
-        mLte = in.readParcelable(CellSignalStrengthLte.class.getClassLoader(), android.telephony.CellSignalStrengthLte.class);
-        mNr = in.readParcelable(CellSignalStrengthLte.class.getClassLoader(), android.telephony.CellSignalStrengthNr.class);
+        mCdma = in.readParcelable(CellSignalStrengthCdma.class.getClassLoader());
+        mGsm = in.readParcelable(CellSignalStrengthGsm.class.getClassLoader());
+        mWcdma = in.readParcelable(CellSignalStrengthWcdma.class.getClassLoader());
+        mTdscdma = in.readParcelable(CellSignalStrengthTdscdma.class.getClassLoader());
+        mLte = in.readParcelable(CellSignalStrengthLte.class.getClassLoader());
+        mNr = in.readParcelable(CellSignalStrengthLte.class.getClassLoader());
         mTimestampMillis = in.readLong();
     }
 
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f5505e6..5ad95b8 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -142,6 +142,7 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.UUID;
 import java.util.concurrent.Executor;
 import java.util.concurrent.RejectedExecutionException;
@@ -6807,6 +6808,24 @@
     }
 
     /**
+     * Get the first active portIndex from the corresponding physical slot index.
+     * @param physicalSlotIndex physical slot index
+     * @return first active port index or INVALID_PORT_INDEX if no port is active
+     */
+    private int getFirstActivePortIndex(int physicalSlotIndex) {
+        UiccSlotInfo[] slotInfos = getUiccSlotsInfo();
+        if (slotInfos != null && physicalSlotIndex >= 0 && physicalSlotIndex < slotInfos.length
+                && slotInfos[physicalSlotIndex] != null) {
+            Optional<UiccPortInfo> result =  slotInfos[physicalSlotIndex].getPorts().stream()
+                    .filter(portInfo -> portInfo.isActive()).findFirst();
+            if (result.isPresent()) {
+                return result.get().getPortIndex();
+            }
+        }
+        return INVALID_PORT_INDEX;
+    }
+
+    /**
      * Opens a logical channel to the ICC card.
      *
      * Input parameters equivalent to TS 27.007 AT+CCHO command.
@@ -6852,7 +6871,8 @@
      * @param p2 P2 parameter (described in ISO 7816-4).
      * @return an IccOpenLogicalChannelResponse object.
      * @hide
-     * @deprecated instead use {@link #iccOpenLogicalChannelByPort(int, int, String, int)}
+     * @deprecated This API is not compatible on eUICC supporting Multiple Enabled Profile(MEP),
+     * instead use {@link #iccOpenLogicalChannelByPort(int, int, String, int)}
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -6866,6 +6886,7 @@
             if (telephony != null) {
                 IccLogicalChannelRequest request = new IccLogicalChannelRequest();
                 request.slotIndex = slotIndex;
+                request.portIndex = getFirstActivePortIndex(slotIndex);
                 request.aid = aid;
                 request.p2 = p2;
                 request.callingPackage = getOpPackageName();
@@ -7021,7 +7042,8 @@
      *            iccOpenLogicalChannel.
      * @return true if the channel was closed successfully.
      * @hide
-     * @deprecated instead use {@link #iccCloseLogicalChannelByPort(int, int, int)}
+     * @deprecated This API is not compatible on eUICC supporting Multiple Enabled Profile(MEP),
+     * instead use {@link #iccCloseLogicalChannelByPort(int, int, int)}
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -7033,6 +7055,7 @@
             if (telephony != null) {
                 IccLogicalChannelRequest request = new IccLogicalChannelRequest();
                 request.slotIndex = slotIndex;
+                request.portIndex = getFirstActivePortIndex(slotIndex);
                 request.channel = channel;
                 return telephony.iccCloseLogicalChannel(request);
             }
@@ -7153,7 +7176,8 @@
      * @return The APDU response from the ICC card with the status appended at the end, or null if
      * there is an issue connecting to the Telephony service.
      * @hide
-     * @deprecated instead use
+     * @deprecated This API is not compatible on eUICC supporting Multiple Enabled Profile(MEP),
+     * instead use
      * {@link #iccTransmitApduLogicalChannelByPort(int, int, int, int, int, int, int, int, String)}
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -7166,8 +7190,9 @@
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                return telephony.iccTransmitApduLogicalChannelByPort(slotIndex, DEFAULT_PORT_INDEX,
-                         channel, cla, instruction, p1, p2, p3, data);
+                return telephony.iccTransmitApduLogicalChannelByPort(slotIndex,
+                        getFirstActivePortIndex(slotIndex), channel, cla, instruction,
+                        p1, p2, p3, data);
             }
         } catch (RemoteException ex) {
         } catch (NullPointerException ex) {
@@ -7305,7 +7330,8 @@
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
      * @hide
-     * @deprecated instead use
+     * @deprecated This API is not compatible on eUICC supporting Multiple Enabled Profile(MEP),
+     * instead use
      * {@link #iccTransmitApduBasicChannelByPort(int, int, int, int, int, int, int, String)}
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -7318,8 +7344,9 @@
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                return telephony.iccTransmitApduBasicChannelByPort(slotIndex, DEFAULT_PORT_INDEX,
-                         getOpPackageName(), cla, instruction, p1, p2, p3, data);
+                return telephony.iccTransmitApduBasicChannelByPort(slotIndex,
+                        getFirstActivePortIndex(slotIndex), getOpPackageName(),
+                        cla, instruction, p1, p2, p3, data);
             }
         } catch (RemoteException ex) {
         } catch (NullPointerException ex) {
diff --git a/telephony/java/android/telephony/ThermalMitigationRequest.java b/telephony/java/android/telephony/ThermalMitigationRequest.java
index a0676ea..91ad9c3 100644
--- a/telephony/java/android/telephony/ThermalMitigationRequest.java
+++ b/telephony/java/android/telephony/ThermalMitigationRequest.java
@@ -100,7 +100,7 @@
 
     private ThermalMitigationRequest(Parcel in) {
         mThermalMitigationAction = in.readInt();
-        mDataThrottlingRequest = in.readParcelable(DataThrottlingRequest.class.getClassLoader(), android.telephony.DataThrottlingRequest.class);
+        mDataThrottlingRequest = in.readParcelable(DataThrottlingRequest.class.getClassLoader());
     }
 
      /**
diff --git a/telephony/java/android/telephony/UiccCardInfo.java b/telephony/java/android/telephony/UiccCardInfo.java
index 74f9c87..464389b 100644
--- a/telephony/java/android/telephony/UiccCardInfo.java
+++ b/telephony/java/android/telephony/UiccCardInfo.java
@@ -156,11 +156,9 @@
     @Nullable
     @Deprecated
     public String getIccId() {
-        // Temporarily bypassing exception
-        // TODO: add exception once refactoring completed.
-        //if (mIccIdAccessRestricted) {
-        //    throw new UnsupportedOperationException("getIccId from UiccPortInfo");
-        //}
+        if (mIccIdAccessRestricted) {
+            throw new UnsupportedOperationException("getIccId from UiccPortInfo");
+        }
         //always return ICCID from first port.
         return getPorts().stream().findFirst().get().getIccId();
     }
diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java
index a8668e7..2b1c8c8 100644
--- a/telephony/java/android/telephony/UiccSlotInfo.java
+++ b/telephony/java/android/telephony/UiccSlotInfo.java
@@ -159,11 +159,9 @@
      */
     @Deprecated
     public boolean getIsActive() {
-        // Temporarily bypassing exception
-        // TODO: add exception once refactoring completed.
-        //if (mLogicalSlotAccessRestricted) {
-        //    throw new UnsupportedOperationException("get port status from UiccPortInfo");
-        //}
+        if (mLogicalSlotAccessRestricted) {
+            throw new UnsupportedOperationException("get port status from UiccPortInfo");
+        }
         //always return status from first port.
         return getPorts().stream().findFirst().get().isActive();
     }
@@ -198,11 +196,9 @@
      */
     @Deprecated
     public int getLogicalSlotIdx() {
-        // Temporarily bypassing exception
-        // TODO: add exception once refactoring completed.
-        //if (mLogicalSlotAccessRestricted) {
-        //    throw new UnsupportedOperationException("get logical slot index from UiccPortInfo");
-        //}
+        if (mLogicalSlotAccessRestricted) {
+            throw new UnsupportedOperationException("get logical slot index from UiccPortInfo");
+        }
         //always return logical slot index from first port.
         //portList always have at least one element.
         return getPorts().stream().findFirst().get().getLogicalSlotIndex();
diff --git a/telephony/java/android/telephony/VisualVoicemailSms.java b/telephony/java/android/telephony/VisualVoicemailSms.java
index bec715e..085f882 100644
--- a/telephony/java/android/telephony/VisualVoicemailSms.java
+++ b/telephony/java/android/telephony/VisualVoicemailSms.java
@@ -121,7 +121,7 @@
                 @Override
                 public VisualVoicemailSms createFromParcel(Parcel in) {
                     return new Builder()
-                            .setPhoneAccountHandle((PhoneAccountHandle) in.readParcelable(null, android.telecom.PhoneAccountHandle.class))
+                            .setPhoneAccountHandle((PhoneAccountHandle) in.readParcelable(null))
                             .setPrefix(in.readString())
                             .setFields(in.readBundle())
                             .setMessageBody(in.readString())
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 4ff59b5..977fe33 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -1629,7 +1629,7 @@
                 .setApnName(in.readString())
                 .setProxyAddress(in.readString())
                 .setProxyPort(in.readInt())
-                .setMmsc(in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class))
+                .setMmsc(in.readParcelable(Uri.class.getClassLoader()))
                 .setMmsProxyAddress(in.readString())
                 .setMmsProxyPort(in.readInt())
                 .setUser(in.readString())
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index ae0d4e7..ef02589 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -241,24 +241,24 @@
         mProtocolType = source.readInt();
         mInterfaceName = source.readString();
         mAddresses = new ArrayList<>();
-        source.readList(mAddresses, LinkAddress.class.getClassLoader(), android.net.LinkAddress.class);
+        source.readList(mAddresses, LinkAddress.class.getClassLoader());
         mDnsAddresses = new ArrayList<>();
-        source.readList(mDnsAddresses, InetAddress.class.getClassLoader(), java.net.InetAddress.class);
+        source.readList(mDnsAddresses, InetAddress.class.getClassLoader());
         mGatewayAddresses = new ArrayList<>();
-        source.readList(mGatewayAddresses, InetAddress.class.getClassLoader(), java.net.InetAddress.class);
+        source.readList(mGatewayAddresses, InetAddress.class.getClassLoader());
         mPcscfAddresses = new ArrayList<>();
-        source.readList(mPcscfAddresses, InetAddress.class.getClassLoader(), java.net.InetAddress.class);
+        source.readList(mPcscfAddresses, InetAddress.class.getClassLoader());
         mMtu = source.readInt();
         mMtuV4 = source.readInt();
         mMtuV6 = source.readInt();
         mHandoverFailureMode = source.readInt();
         mPduSessionId = source.readInt();
-        mDefaultQos = source.readParcelable(Qos.class.getClassLoader(), android.telephony.data.Qos.class);
+        mDefaultQos = source.readParcelable(Qos.class.getClassLoader());
         mQosBearerSessions = new ArrayList<>();
-        source.readList(mQosBearerSessions, QosBearerSession.class.getClassLoader(), android.telephony.data.QosBearerSession.class);
-        mSliceInfo = source.readParcelable(NetworkSliceInfo.class.getClassLoader(), android.telephony.data.NetworkSliceInfo.class);
+        source.readList(mQosBearerSessions, QosBearerSession.class.getClassLoader());
+        mSliceInfo = source.readParcelable(NetworkSliceInfo.class.getClassLoader());
         mTrafficDescriptors = new ArrayList<>();
-        source.readList(mTrafficDescriptors, TrafficDescriptor.class.getClassLoader(), android.telephony.data.TrafficDescriptor.class);
+        source.readList(mTrafficDescriptors, TrafficDescriptor.class.getClassLoader());
     }
 
     /**
diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java
index a166a5d..ec04c1a 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -107,8 +107,8 @@
 
     private DataProfile(Parcel source) {
         mType = source.readInt();
-        mApnSetting = source.readParcelable(ApnSetting.class.getClassLoader(), android.telephony.data.ApnSetting.class);
-        mTrafficDescriptor = source.readParcelable(TrafficDescriptor.class.getClassLoader(), android.telephony.data.TrafficDescriptor.class);
+        mApnSetting = source.readParcelable(ApnSetting.class.getClassLoader());
+        mTrafficDescriptor = source.readParcelable(TrafficDescriptor.class.getClassLoader());
         mPreferred = source.readBoolean();
         mSetupTimestamp = source.readLong();
     }
diff --git a/telephony/java/android/telephony/data/Qos.java b/telephony/java/android/telephony/data/Qos.java
index 9c2a3bb..8c437c8 100644
--- a/telephony/java/android/telephony/data/Qos.java
+++ b/telephony/java/android/telephony/data/Qos.java
@@ -136,8 +136,8 @@
 
     protected Qos(@NonNull Parcel source) {
         type = source.readInt();
-        downlink = source.readParcelable(QosBandwidth.class.getClassLoader(), android.telephony.data.Qos.QosBandwidth.class);
-        uplink = source.readParcelable(QosBandwidth.class.getClassLoader(), android.telephony.data.Qos.QosBandwidth.class);
+        downlink = source.readParcelable(QosBandwidth.class.getClassLoader());
+        uplink = source.readParcelable(QosBandwidth.class.getClassLoader());
     }
 
     /**
diff --git a/telephony/java/android/telephony/data/QosBearerFilter.java b/telephony/java/android/telephony/data/QosBearerFilter.java
index 0ab7b61..d6f0cb0 100644
--- a/telephony/java/android/telephony/data/QosBearerFilter.java
+++ b/telephony/java/android/telephony/data/QosBearerFilter.java
@@ -256,11 +256,11 @@
 
     private QosBearerFilter(Parcel source) {
         localAddresses = new ArrayList<>();
-        source.readList(localAddresses, LinkAddress.class.getClassLoader(), android.net.LinkAddress.class);
+        source.readList(localAddresses, LinkAddress.class.getClassLoader());
         remoteAddresses = new ArrayList<>();
-        source.readList(remoteAddresses, LinkAddress.class.getClassLoader(), android.net.LinkAddress.class);
-        localPort = source.readParcelable(PortRange.class.getClassLoader(), android.telephony.data.QosBearerFilter.PortRange.class);
-        remotePort = source.readParcelable(PortRange.class.getClassLoader(), android.telephony.data.QosBearerFilter.PortRange.class);
+        source.readList(remoteAddresses, LinkAddress.class.getClassLoader());
+        localPort = source.readParcelable(PortRange.class.getClassLoader());
+        remotePort = source.readParcelable(PortRange.class.getClassLoader());
         protocol = source.readInt();
         typeOfServiceMask = source.readInt();
         flowLabel = source.readLong();
diff --git a/telephony/java/android/telephony/data/QosBearerSession.java b/telephony/java/android/telephony/data/QosBearerSession.java
index dd08085..ffeb08a 100644
--- a/telephony/java/android/telephony/data/QosBearerSession.java
+++ b/telephony/java/android/telephony/data/QosBearerSession.java
@@ -46,9 +46,9 @@
 
     private QosBearerSession(Parcel source) {
         qosBearerSessionId = source.readInt();
-        qos = source.readParcelable(Qos.class.getClassLoader(), android.telephony.data.Qos.class);
+        qos = source.readParcelable(Qos.class.getClassLoader());
         qosBearerFilterList = new ArrayList<>();
-        source.readList(qosBearerFilterList, QosBearerFilter.class.getClassLoader(), android.telephony.data.QosBearerFilter.class);
+        source.readList(qosBearerFilterList, QosBearerFilter.class.getClassLoader());
     }
 
     public int getQosBearerSessionId() {
diff --git a/telephony/java/android/telephony/gba/GbaAuthRequest.java b/telephony/java/android/telephony/gba/GbaAuthRequest.java
index 2c6021a..5366e9a 100644
--- a/telephony/java/android/telephony/gba/GbaAuthRequest.java
+++ b/telephony/java/android/telephony/gba/GbaAuthRequest.java
@@ -120,7 +120,7 @@
                     int token = in.readInt();
                     int subId = in.readInt();
                     int appType = in.readInt();
-                    Uri nafUrl = in.readParcelable(GbaAuthRequest.class.getClassLoader(), android.net.Uri.class);
+                    Uri nafUrl = in.readParcelable(GbaAuthRequest.class.getClassLoader());
                     int len = in.readInt();
                     byte[] protocol = new byte[len];
                     in.readByteArray(protocol);
diff --git a/telephony/java/android/telephony/ims/DelegateRequest.java b/telephony/java/android/telephony/ims/DelegateRequest.java
index c5c9200..c322d92 100644
--- a/telephony/java/android/telephony/ims/DelegateRequest.java
+++ b/telephony/java/android/telephony/ims/DelegateRequest.java
@@ -63,7 +63,7 @@
      */
     private DelegateRequest(Parcel in) {
         mFeatureTags = new ArrayList<>();
-        in.readList(mFeatureTags, null /*classLoader*/, java.lang.String.class);
+        in.readList(mFeatureTags, null /*classLoader*/);
     }
 
     @Override
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index 7e2d80e..8a665dc 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -843,7 +843,7 @@
         mServiceType = in.readInt();
         mCallType = in.readInt();
         mCallExtras = in.readBundle();
-        mMediaProfile = in.readParcelable(ImsStreamMediaProfile.class.getClassLoader(), android.telephony.ims.ImsStreamMediaProfile.class);
+        mMediaProfile = in.readParcelable(ImsStreamMediaProfile.class.getClassLoader());
         mEmergencyServiceCategories = in.readInt();
         mEmergencyUrns = in.createStringArrayList();
         mEmergencyCallRouting = in.readInt();
@@ -851,7 +851,8 @@
         mHasKnownUserIntentEmergency = in.readBoolean();
         mRestrictCause = in.readInt();
         mCallerNumberVerificationStatus = in.readInt();
-        Object[] accepted = in.readArray(RtpHeaderExtensionType.class.getClassLoader());
+        Object[] accepted = in.readArray(RtpHeaderExtensionType.class.getClassLoader(),
+                RtpHeaderExtensionType.class);
         mAcceptedRtpHeaderExtensionTypes = Arrays.stream(accepted)
                 .map(o -> (RtpHeaderExtensionType) o).collect(Collectors.toSet());
     }
diff --git a/telephony/java/android/telephony/ims/ImsConferenceState.java b/telephony/java/android/telephony/ims/ImsConferenceState.java
index d4d8c44..1fa5f52 100644
--- a/telephony/java/android/telephony/ims/ImsConferenceState.java
+++ b/telephony/java/android/telephony/ims/ImsConferenceState.java
@@ -133,7 +133,7 @@
 
         for (int i = 0; i < size; ++i) {
             String user = in.readString();
-            Bundle state = in.readParcelable(null, android.os.Bundle.class);
+            Bundle state = in.readParcelable(null);
             mParticipants.put(user, state);
         }
     }
diff --git a/telephony/java/android/telephony/ims/ImsExternalCallState.java b/telephony/java/android/telephony/ims/ImsExternalCallState.java
index d451107..c663e39 100644
--- a/telephony/java/android/telephony/ims/ImsExternalCallState.java
+++ b/telephony/java/android/telephony/ims/ImsExternalCallState.java
@@ -141,8 +141,8 @@
     public ImsExternalCallState(Parcel in) {
         mCallId = in.readInt();
         ClassLoader classLoader = ImsExternalCallState.class.getClassLoader();
-        mAddress = in.readParcelable(classLoader, android.net.Uri.class);
-        mLocalAddress = in.readParcelable(classLoader, android.net.Uri.class);
+        mAddress = in.readParcelable(classLoader);
+        mLocalAddress = in.readParcelable(classLoader);
         mIsPullable = (in.readInt() != 0);
         mCallState = in.readInt();
         mCallType = in.readInt();
diff --git a/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java
index b77d306..ccb3231 100644
--- a/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java
+++ b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java
@@ -153,7 +153,7 @@
         mTransportType = source.readInt();
         mImsAttributeFlags = source.readInt();
         mFeatureTags = new ArrayList<>();
-        source.readList(mFeatureTags, null /*classloader*/, java.lang.String.class);
+        source.readList(mFeatureTags, null /*classloader*/);
     }
 
     /**
diff --git a/telephony/java/android/telephony/ims/ImsSsData.java b/telephony/java/android/telephony/ims/ImsSsData.java
index 9f4b77e..868dea6a 100644
--- a/telephony/java/android/telephony/ims/ImsSsData.java
+++ b/telephony/java/android/telephony/ims/ImsSsData.java
@@ -365,8 +365,8 @@
         serviceClass = in.readInt();
         result = in.readInt();
         mSsInfo = in.createIntArray();
-        mCfInfo = in.readParcelableList(new ArrayList<>(), this.getClass().getClassLoader(), android.telephony.ims.ImsCallForwardInfo.class);
-        mImsSsInfo = in.readParcelableList(new ArrayList<>(), this.getClass().getClassLoader(), android.telephony.ims.ImsSsInfo.class);
+        mCfInfo = in.readParcelableList(new ArrayList<>(), this.getClass().getClassLoader());
+        mImsSsInfo = in.readParcelableList(new ArrayList<>(), this.getClass().getClassLoader());
     }
 
     public static final @android.annotation.NonNull Creator<ImsSsData> CREATOR = new Creator<ImsSsData>() {
diff --git a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
index 6a6c306..9c28c36 100644
--- a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
+++ b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
@@ -439,13 +439,13 @@
     }
 
     private RcsContactPresenceTuple(Parcel in) {
-        mContactUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
+        mContactUri = in.readParcelable(Uri.class.getClassLoader());
         mTimestamp = convertStringFormatTimeToInstant(in.readString());
         mStatus = in.readString();
         mServiceId = in.readString();
         mServiceVersion = in.readString();
         mServiceDescription = in.readString();
-        mServiceCapabilities = in.readParcelable(ServiceCapabilities.class.getClassLoader(), android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities.class);
+        mServiceCapabilities = in.readParcelable(ServiceCapabilities.class.getClassLoader());
     }
 
     @Override
diff --git a/telephony/java/android/telephony/ims/RcsContactTerminatedReason.java b/telephony/java/android/telephony/ims/RcsContactTerminatedReason.java
index ea022de..ee02564 100644
--- a/telephony/java/android/telephony/ims/RcsContactTerminatedReason.java
+++ b/telephony/java/android/telephony/ims/RcsContactTerminatedReason.java
@@ -37,7 +37,7 @@
     }
 
     private RcsContactTerminatedReason(Parcel in) {
-        mContactUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
+        mContactUri = in.readParcelable(Uri.class.getClassLoader());
         mReason = in.readString();
     }
 
diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
index 0f1b369..9112118 100644
--- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
@@ -244,14 +244,14 @@
     }
 
     private RcsContactUceCapability(Parcel in) {
-        mContactUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
+        mContactUri = in.readParcelable(Uri.class.getClassLoader());
         mCapabilityMechanism = in.readInt();
         mSourceType = in.readInt();
         mRequestResult = in.readInt();
         List<String> featureTagList = new ArrayList<>();
         in.readStringList(featureTagList);
         mFeatureTags.addAll(featureTagList);
-        in.readParcelableList(mPresenceTuples, RcsContactPresenceTuple.class.getClassLoader(), android.telephony.ims.RcsContactPresenceTuple.class);
+        in.readParcelableList(mPresenceTuples, RcsContactPresenceTuple.class.getClassLoader());
     }
 
     @Override
diff --git a/telephony/java/android/telephony/ims/RtpHeaderExtensionType.java b/telephony/java/android/telephony/ims/RtpHeaderExtensionType.java
index b9ffd24..af4e2347 100644
--- a/telephony/java/android/telephony/ims/RtpHeaderExtensionType.java
+++ b/telephony/java/android/telephony/ims/RtpHeaderExtensionType.java
@@ -63,7 +63,7 @@
 
     private RtpHeaderExtensionType(Parcel in) {
         mLocalIdentifier = in.readInt();
-        mUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
+        mUri = in.readParcelable(Uri.class.getClassLoader());
     }
 
     public static final @NonNull Creator<RtpHeaderExtensionType> CREATOR =
diff --git a/telephony/java/android/telephony/ims/SipDelegateConfiguration.java b/telephony/java/android/telephony/ims/SipDelegateConfiguration.java
index db0ae03..1bf5cad 100644
--- a/telephony/java/android/telephony/ims/SipDelegateConfiguration.java
+++ b/telephony/java/android/telephony/ims/SipDelegateConfiguration.java
@@ -573,7 +573,7 @@
         mPrivateUserIdentifier = source.readString();
         mHomeDomain = source.readString();
         mImei = source.readString();
-        mGruu = source.readParcelable(null, android.net.Uri.class);
+        mGruu = source.readParcelable(null);
         mSipAuthHeader = source.readString();
         mSipAuthNonce = source.readString();
         mServiceRouteHeader = source.readString();
diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java
index 81d5be8..eb59f87 100644
--- a/telephony/java/android/telephony/mbms/DownloadRequest.java
+++ b/telephony/java/android/telephony/mbms/DownloadRequest.java
@@ -242,8 +242,8 @@
 
     private DownloadRequest(Parcel in) {
         fileServiceId = in.readString();
-        sourceUri = in.readParcelable(getClass().getClassLoader(), android.net.Uri.class);
-        destinationUri = in.readParcelable(getClass().getClassLoader(), android.net.Uri.class);
+        sourceUri = in.readParcelable(getClass().getClassLoader());
+        destinationUri = in.readParcelable(getClass().getClassLoader());
         subscriptionId = in.readInt();
         serializedResultIntentForApp = in.readString();
         version = in.readInt();
diff --git a/telephony/java/android/telephony/mbms/FileInfo.java b/telephony/java/android/telephony/mbms/FileInfo.java
index ffd864e..e52b2ce 100644
--- a/telephony/java/android/telephony/mbms/FileInfo.java
+++ b/telephony/java/android/telephony/mbms/FileInfo.java
@@ -55,7 +55,7 @@
     }
 
     private FileInfo(Parcel in) {
-        uri = in.readParcelable(null, android.net.Uri.class);
+        uri = in.readParcelable(null);
         mimeType = in.readString();
     }
 
diff --git a/telephony/java/android/telephony/mbms/FileServiceInfo.java b/telephony/java/android/telephony/mbms/FileServiceInfo.java
index 0fc3be6..8777e7f 100644
--- a/telephony/java/android/telephony/mbms/FileServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/FileServiceInfo.java
@@ -58,7 +58,7 @@
     FileServiceInfo(Parcel in) {
         super(in);
         files = new ArrayList<FileInfo>();
-        in.readList(files, FileInfo.class.getClassLoader(), android.telephony.mbms.FileInfo.class);
+        in.readList(files, FileInfo.class.getClassLoader());
     }
 
     @Override
diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.java b/telephony/java/android/telephony/mbms/ServiceInfo.java
index 02424ff..f78e7a6 100644
--- a/telephony/java/android/telephony/mbms/ServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/ServiceInfo.java
@@ -80,7 +80,7 @@
         }
         names = new HashMap(mapCount);
         while (mapCount-- > 0) {
-            Locale locale = (java.util.Locale) in.readSerializable(java.util.Locale.class.getClassLoader(), java.util.Locale.class);
+            Locale locale = (java.util.Locale) in.readSerializable();
             String name = in.readString();
             names.put(locale, name);
         }
@@ -91,12 +91,12 @@
         }
         locales = new ArrayList<Locale>(localesCount);
         while (localesCount-- > 0) {
-            Locale l = (java.util.Locale) in.readSerializable(java.util.Locale.class.getClassLoader(), java.util.Locale.class);
+            Locale l = (java.util.Locale) in.readSerializable();
             locales.add(l);
         }
         serviceId = in.readString();
-        sessionStartTime = (java.util.Date) in.readSerializable(java.util.Date.class.getClassLoader(), java.util.Date.class);
-        sessionEndTime = (java.util.Date) in.readSerializable(java.util.Date.class.getClassLoader(), java.util.Date.class);
+        sessionStartTime = (java.util.Date) in.readSerializable();
+        sessionEndTime = (java.util.Date) in.readSerializable();
     }
 
     /** @hide */
diff --git a/telephony/java/android/telephony/mbms/UriPathPair.java b/telephony/java/android/telephony/mbms/UriPathPair.java
index 54d9d9e..9258919 100644
--- a/telephony/java/android/telephony/mbms/UriPathPair.java
+++ b/telephony/java/android/telephony/mbms/UriPathPair.java
@@ -48,8 +48,8 @@
 
     /** @hide */
     private UriPathPair(Parcel in) {
-        mFilePathUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
-        mContentUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
+        mFilePathUri = in.readParcelable(Uri.class.getClassLoader());
+        mContentUri = in.readParcelable(Uri.class.getClassLoader());
     }
 
     public static final @android.annotation.NonNull Creator<UriPathPair> CREATOR = new Creator<UriPathPair>() {
diff --git a/telephony/java/com/android/internal/telephony/NetworkScanResult.java b/telephony/java/com/android/internal/telephony/NetworkScanResult.java
index 8b49f4b..d07d77c 100644
--- a/telephony/java/com/android/internal/telephony/NetworkScanResult.java
+++ b/telephony/java/com/android/internal/telephony/NetworkScanResult.java
@@ -83,7 +83,7 @@
         scanStatus = in.readInt();
         scanError = in.readInt();
         List<CellInfo> ni = new ArrayList<>();
-        in.readParcelableList(ni, Object.class.getClassLoader(), android.telephony.CellInfo.class);
+        in.readParcelableList(ni, Object.class.getClassLoader());
         networkInfos = ni;
     }
 
diff --git a/telephony/java/com/android/internal/telephony/OperatorInfo.java b/telephony/java/com/android/internal/telephony/OperatorInfo.java
index 1820a1d..a6f0f66 100644
--- a/telephony/java/com/android/internal/telephony/OperatorInfo.java
+++ b/telephony/java/com/android/internal/telephony/OperatorInfo.java
@@ -189,7 +189,7 @@
                             in.readString(), /*operatorAlphaLong*/
                             in.readString(), /*operatorAlphaShort*/
                             in.readString(), /*operatorNumeric*/
-                            (State) in.readSerializable(com.android.internal.telephony.OperatorInfo.State.class.getClassLoader(), com.android.internal.telephony.OperatorInfo.State.class), /*state*/
+                            (State) in.readSerializable(), /*state*/
                             in.readInt()); /*ran*/
                     return opInfo;
                 }
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index ba95841..39ab7eb 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -592,6 +592,7 @@
     int RIL_UNSOL_UNTHROTTLE_APN = 1052;
     int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED = 1053;
     int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED = 1054;
+    int RIL_UNSOL_SLICING_CONFIG_CHANGED = 1055;
 
     /* The following unsols are not defined in RIL.h */
     int RIL_UNSOL_HAL_NON_RIL_BASE = 1100;
diff --git a/tests/AppLaunchWear/Android.bp b/tests/AppLaunchWear/Android.bp
deleted file mode 100644
index e2fc473..0000000
--- a/tests/AppLaunchWear/Android.bp
+++ /dev/null
@@ -1,22 +0,0 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test {
-    name: "AppLaunchWear",
-    // Only compile source java files in this apk.
-    srcs: ["src/**/*.java"],
-    platform_apis: true,
-    certificate: "platform",
-    libs: [
-        "android.test.base",
-        "android.test.runner",
-    ],
-    static_libs: ["androidx.test.rules"],
-    test_suites: ["device-tests"],
-}
diff --git a/tests/AppLaunchWear/AndroidManifest.xml b/tests/AppLaunchWear/AndroidManifest.xml
deleted file mode 100644
index 7dfd7ba..0000000
--- a/tests/AppLaunchWear/AndroidManifest.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.tests.applaunch"
-    android:sharedUserId="android.uid.system" >
-
-   <uses-permission android:name="android.permission.REAL_GET_TASKS" />
-   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
-   <uses-sdk
-        android:minSdkVersion="22"
-        android:targetSdkVersion="24" />
-
-    <instrumentation android:label="Measure app start up time"
-                     android:name="android.test.InstrumentationTestRunner"
-                     android:targetPackage="com.android.tests.applaunch" />
-
-    <application android:label="App Launch Test">
-        <uses-library android:name="android.test.runner" />
-    </application>
-</manifest>
diff --git a/tests/AppLaunchWear/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunchWear/src/com/android/tests/applaunch/AppLaunch.java
deleted file mode 100644
index 97701c6..0000000
--- a/tests/AppLaunchWear/src/com/android/tests/applaunch/AppLaunch.java
+++ /dev/null
@@ -1,864 +0,0 @@
-/*
- * Copyright (C) 2013 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.tests.applaunch;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.app.ActivityManager;
-import android.app.ActivityManager.ProcessErrorStateInfo;
-import android.app.IActivityManager;
-import android.app.UiAutomation;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.test.InstrumentationTestCase;
-import android.test.InstrumentationTestRunner;
-import android.util.Log;
-
-import androidx.test.rule.logging.AtraceLogger;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * This test is intended to measure the time it takes for the apps to start.
- * Names of the applications are passed in command line, and the
- * test starts each application, and reports the start up time in milliseconds.
- * The instrumentation expects the following key to be passed on the command line:
- * apps - A list of applications to start and their corresponding result keys
- * in the following format:
- * -e apps <app name>^<result key>|<app name>^<result key>
- */
-public class AppLaunch extends InstrumentationTestCase {
-
-    private static final int JOIN_TIMEOUT = 10000;
-    private static final String TAG = AppLaunch.class.getSimpleName();
-
-    // optional parameter: comma separated list of required account types before proceeding
-    // with the app launch
-    private static final String KEY_REQUIRED_ACCOUNTS = "required_accounts";
-    private static final String KEY_APPS = "apps";
-    private static final String KEY_TRIAL_LAUNCH = "trial_launch";
-    private static final String KEY_LAUNCH_ITERATIONS = "launch_iterations";
-    private static final String KEY_LAUNCH_ORDER = "launch_order";
-    private static final String KEY_DROP_CACHE = "drop_cache";
-    private static final String KEY_SIMPLEPERF_CMD = "simpleperf_cmd";
-    private static final String KEY_SIMPLEPERF_APP = "simpleperf_app";
-    private static final String KEY_TRACE_ITERATIONS = "trace_iterations";
-    private static final String KEY_LAUNCH_DIRECTORY = "launch_directory";
-    private static final String KEY_TRACE_DIRECTORY = "trace_directory";
-    private static final String KEY_TRACE_CATEGORY = "trace_categories";
-    private static final String KEY_TRACE_BUFFERSIZE = "trace_bufferSize";
-    private static final String KEY_TRACE_DUMPINTERVAL = "tracedump_interval";
-    private static final String KEY_COMPILER_FILTERS = "compiler_filters";
-
-    private static final String SIMPLEPERF_APP_CMD =
-            "simpleperf --log fatal stat --csv -e cpu-cycles,major-faults --app %s & %s";
-    private static final String WEARABLE_ACTION_GOOGLE =
-            "com.google.android.wearable.action.GOOGLE";
-    private static final int INITIAL_LAUNCH_IDLE_TIMEOUT = 5000; // 5s to allow app to idle
-    private static final int POST_LAUNCH_IDLE_TIMEOUT = 750; // 750ms idle for non initial launches
-    private static final int BETWEEN_LAUNCH_SLEEP_TIMEOUT = 5000; // 5s between launching apps
-    private static final String LAUNCH_SUB_DIRECTORY = "launch_logs";
-    private static final String LAUNCH_FILE = "applaunch.txt";
-    private static final String TRACE_SUB_DIRECTORY = "atrace_logs";
-    private static final String DEFAULT_TRACE_CATEGORIES =
-            "sched,freq,gfx,view,dalvik,webview,input,wm,disk,am,wm";
-    private static final String DEFAULT_TRACE_BUFFER_SIZE = "20000";
-    private static final String DEFAULT_TRACE_DUMP_INTERVAL = "10";
-    private static final String TRIAL_LAUNCH = "TRIAL_LAUNCH";
-    private static final String DELIMITER = ",";
-    private static final String DROP_CACHE_SCRIPT = "/data/local/tmp/dropCache.sh";
-    private static final String APP_LAUNCH_CMD = "am start -W -n";
-    private static final String SUCCESS_MESSAGE = "Status: ok";
-    private static final String WARNING_MESSAGE = "Warning: Activity not started";
-    private static final String COMPILE_SUCCESS = "Success";
-    private static final String THIS_TIME = "ThisTime:";
-    private static final String LAUNCH_ITERATION = "LAUNCH_ITERATION - %d";
-    private static final String TRACE_ITERATION = "TRACE_ITERATION-%d";
-    private static final String LAUNCH_ITERATION_PREFIX = "LAUNCH_ITERATION";
-    private static final String TRACE_ITERATION_PREFIX = "TRACE_ITERATION";
-    private static final String LAUNCH_ORDER_CYCLIC = "cyclic";
-    private static final String LAUNCH_ORDER_SEQUENTIAL = "sequential";
-    private static final String COMPILE_CMD = "cmd package compile -f -m %s %s";
-    private static final String SPEED_PROFILE_FILTER = "speed-profile";
-    private static final String VERIFY_FILTER = "verify";
-    private static final String LAUNCH_SCRIPT_NAME = "appLaunch";
-    private static final String WEARABLE_HOME_PACKAGE = "com.google.android.wearable.app";
-
-    private Map<String, Intent> mNameToIntent;
-    private List<LaunchOrder> mLaunchOrderList = new ArrayList<LaunchOrder>();
-    private Map<String, String> mNameToResultKey;
-    private Map<String, Map<String, List<AppLaunchResult>>> mNameToLaunchTime;
-    private IActivityManager mAm;
-    private String mSimplePerfCmd = null;
-    private String mLaunchOrder = null;
-    private boolean mDropCache = false;
-    private int mLaunchIterations = 10;
-    private int mTraceLaunchCount = 0;
-    private String mTraceDirectoryStr = null;
-    private Bundle mResult = new Bundle();
-    private Set<String> mRequiredAccounts;
-    private boolean mTrialLaunch = false;
-    private BufferedWriter mBufferedWriter = null;
-    private boolean mSimplePerfAppOnly = false;
-    private String[] mCompilerFilters = null;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_0);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_UNFREEZE);
-        super.tearDown();
-    }
-
-    private void addLaunchResult(LaunchOrder launch, AppLaunchResult result) {
-        mNameToLaunchTime.get(launch.getApp()).get(launch.getCompilerFilter()).add(result);
-    }
-
-    private boolean hasFailureOnFirstLaunch(LaunchOrder launch) {
-        List<AppLaunchResult> results =
-            mNameToLaunchTime.get(launch.getApp()).get(launch.getCompilerFilter());
-        return (results.size() > 0) && (results.get(0).mLaunchTime < 0);
-    }
-
-    public void testMeasureStartUpTime() throws RemoteException, NameNotFoundException,
-            IOException, InterruptedException {
-        InstrumentationTestRunner instrumentation =
-                (InstrumentationTestRunner)getInstrumentation();
-        Bundle args = instrumentation.getArguments();
-        mAm = ActivityManager.getService();
-        String launchDirectory = args.getString(KEY_LAUNCH_DIRECTORY);
-
-        createMappings();
-        parseArgs(args);
-        checkAccountSignIn();
-
-        // Root directory for applaunch file to log the app launch output
-        // Will be useful in case of simpleperf command is used
-        File launchRootDir = null;
-        if (null != launchDirectory && !launchDirectory.isEmpty()) {
-            launchRootDir = new File(launchDirectory);
-            if (!launchRootDir.exists() && !launchRootDir.mkdirs()) {
-                throw new IOException("Unable to create the destination directory");
-            }
-        }
-
-        try {
-            File launchSubDir = new File(launchRootDir, LAUNCH_SUB_DIRECTORY);
-
-            if (!launchSubDir.exists() && !launchSubDir.mkdirs()) {
-                throw new IOException("Unable to create the lauch file sub directory");
-            }
-            File file = new File(launchSubDir, LAUNCH_FILE);
-            FileOutputStream outputStream = new FileOutputStream(file);
-            mBufferedWriter = new BufferedWriter(new OutputStreamWriter(
-                    outputStream));
-
-            // Root directory for trace file during the launches
-            File rootTrace = null;
-            File rootTraceSubDir = null;
-            int traceBufferSize = 0;
-            int traceDumpInterval = 0;
-            Set<String> traceCategoriesSet = null;
-            if (null != mTraceDirectoryStr && !mTraceDirectoryStr.isEmpty()) {
-                rootTrace = new File(mTraceDirectoryStr);
-                if (!rootTrace.exists() && !rootTrace.mkdirs()) {
-                    throw new IOException("Unable to create the trace directory");
-                }
-                rootTraceSubDir = new File(rootTrace, TRACE_SUB_DIRECTORY);
-                if (!rootTraceSubDir.exists() && !rootTraceSubDir.mkdirs()) {
-                    throw new IOException("Unable to create the trace sub directory");
-                }
-                assertNotNull("Trace iteration parameter is mandatory",
-                        args.getString(KEY_TRACE_ITERATIONS));
-                mTraceLaunchCount = Integer.parseInt(args.getString(KEY_TRACE_ITERATIONS));
-                String traceCategoriesStr = args
-                        .getString(KEY_TRACE_CATEGORY, DEFAULT_TRACE_CATEGORIES);
-                traceBufferSize = Integer.parseInt(args.getString(KEY_TRACE_BUFFERSIZE,
-                        DEFAULT_TRACE_BUFFER_SIZE));
-                traceDumpInterval = Integer.parseInt(args.getString(KEY_TRACE_DUMPINTERVAL,
-                        DEFAULT_TRACE_DUMP_INTERVAL));
-                traceCategoriesSet = new HashSet<String>();
-                if (!traceCategoriesStr.isEmpty()) {
-                    String[] traceCategoriesSplit = traceCategoriesStr.split(DELIMITER);
-                    for (int i = 0; i < traceCategoriesSplit.length; i++) {
-                        traceCategoriesSet.add(traceCategoriesSplit[i]);
-                    }
-                }
-            }
-
-            // Get the app launch order based on launch order, trial launch,
-            // launch iterations and trace iterations
-            setLaunchOrder();
-
-            for (LaunchOrder launch : mLaunchOrderList) {
-                if (mNameToIntent.get(launch.getApp()) == null) {
-                    continue;
-                }
-                dropCache();
-                String appPkgName = mNameToIntent.get(launch.getApp())
-                        .getComponent().getPackageName();
-                Log.v(TAG, String.format("\nApp name: %s", launch.getApp()));
-                Log.v(TAG, String.format("Adding app package name: %s", appPkgName));
-                // App launch times for trial launch will not be used for final
-                // launch time calculations.
-                if (launch.getLaunchReason().equals(TRIAL_LAUNCH)) {
-                    // In the "applaunch.txt" file, trail launches is referenced using
-                    // "TRIAL_LAUNCH"
-                    Log.v(TAG, "Trial Launch");
-                    if (SPEED_PROFILE_FILTER.equals(launch.getCompilerFilter())) {
-                        assertTrue(String.format("Not able to compile the app : %s", appPkgName),
-                              compileApp(VERIFY_FILTER, appPkgName));
-                    } else if (launch.getCompilerFilter() != null) {
-                        assertTrue(String.format("Not able to compile the app : %s", appPkgName),
-                              compileApp(launch.getCompilerFilter(), appPkgName));
-                    }
-                    // We only need to run a trial for the speed-profile filter, but we always
-                    // run one for "applaunch.txt" consistency.
-                    AppLaunchResult launchResult = null;
-                    if (appPkgName.contains(WEARABLE_HOME_PACKAGE)) {
-                        Log.v(TAG, "Home package detected. Not killing app");
-                        launchResult = startApp(launch.getApp(), false, launch.getLaunchReason());
-                    } else {
-                        Log.v(TAG, "Will kill app before launch");
-                        launchResult = startApp(launch.getApp(), true, launch.getLaunchReason());
-                    }
-                    if (launchResult.mLaunchTime < 0) {
-                        addLaunchResult(launch, new AppLaunchResult());
-                        // simply pass the app if launch isn't successful
-                        // error should have already been logged by startApp
-                        continue;
-                    }
-                    sleep(INITIAL_LAUNCH_IDLE_TIMEOUT);
-                    if (SPEED_PROFILE_FILTER.equals(launch.getCompilerFilter())) {
-                        // Send SIGUSR1 to force dumping a profile.
-                        String sendSignalCommand =
-                            String.format("killall -s SIGUSR1 %s", appPkgName);
-                        getInstrumentation().getUiAutomation().executeShellCommand(
-                            sendSignalCommand);
-                        assertTrue(String.format("Not able to compile the app : %s", appPkgName),
-                              compileApp(launch.getCompilerFilter(), appPkgName));
-                    }
-                }
-
-                // App launch times used for final calculation
-                else if (launch.getLaunchReason().contains(LAUNCH_ITERATION_PREFIX)) {
-                    Log.v(TAG, "Launch iteration prefix.");
-                    AppLaunchResult launchResults = null;
-                    if (hasFailureOnFirstLaunch(launch)) {
-                        // skip if the app has failures while launched first
-                        continue;
-                    }
-                    // In the "applaunch.txt" file app launches are referenced using
-                    // "LAUNCH_ITERATION - ITERATION NUM"
-                    if (appPkgName.contains(WEARABLE_HOME_PACKAGE)) {
-                        Log.v(TAG, "Home package detected. Not killing app");
-                        launchResults = startApp(launch.getApp(), false, launch.getLaunchReason());
-                    } else {
-                        Log.v(TAG, "Will kill app before launch");
-                        launchResults = startApp(launch.getApp(), true, launch.getLaunchReason());
-                    }
-                    if (launchResults.mLaunchTime < 0) {
-                        addLaunchResult(launch, new AppLaunchResult());
-                        // if it fails once, skip the rest of the launches
-                        continue;
-                    } else {
-                        addLaunchResult(launch, launchResults);
-                    }
-                    sleep(POST_LAUNCH_IDLE_TIMEOUT);
-                }
-
-                // App launch times for trace launch will not be used for final
-                // launch time calculations.
-                else if (launch.getLaunchReason().contains(TRACE_ITERATION_PREFIX)) {
-                    Log.v(TAG, "Trace iteration prefix");
-                    AtraceLogger atraceLogger = AtraceLogger
-                            .getAtraceLoggerInstance(getInstrumentation());
-                    // Start the trace
-                    try {
-                        atraceLogger.atraceStart(traceCategoriesSet, traceBufferSize,
-                                traceDumpInterval, rootTraceSubDir,
-                                String.format("%s-%s", launch.getApp(), launch.getLaunchReason()));
-                        if (appPkgName.contains(WEARABLE_HOME_PACKAGE)) {
-                            Log.v(TAG, "Home package detected. Not killing app");
-                            startApp(launch.getApp(), false, launch.getLaunchReason());
-                        } else {
-                            Log.v(TAG, "Will kill app before launch");
-                            startApp(launch.getApp(), true, launch.getLaunchReason());
-                        }
-                        sleep(POST_LAUNCH_IDLE_TIMEOUT);
-                    } finally {
-                        // Stop the trace
-                        atraceLogger.atraceStop();
-                    }
-                }
-                closeApp(launch.getApp(), true);
-                sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT);
-            }
-        } finally {
-            if (null != mBufferedWriter) {
-                mBufferedWriter.close();
-            }
-        }
-
-        for (String app : mNameToResultKey.keySet()) {
-            for (String compilerFilter : mCompilerFilters) {
-                StringBuilder launchTimes = new StringBuilder();
-                StringBuilder cpuCycles = new StringBuilder();
-                StringBuilder majorFaults = new StringBuilder();
-                for (AppLaunchResult result : mNameToLaunchTime.get(app).get(compilerFilter)) {
-                    launchTimes.append(result.mLaunchTime);
-                    launchTimes.append(",");
-                    if (mSimplePerfAppOnly) {
-                        cpuCycles.append(result.mCpuCycles);
-                        cpuCycles.append(",");
-                        majorFaults.append(result.mMajorFaults);
-                        majorFaults.append(",");
-                    }
-                }
-                String filterName = (compilerFilter == null) ? "" : ("-" + compilerFilter);
-                mResult.putString(mNameToResultKey.get(app) + filterName, launchTimes.toString());
-                if (mSimplePerfAppOnly) {
-                    mResult.putString(mNameToResultKey.get(app) + filterName + "-cpuCycles",
-                        cpuCycles.toString());
-                    mResult.putString(mNameToResultKey.get(app) + filterName + "-majorFaults",
-                        majorFaults.toString());
-                }
-            }
-        }
-        instrumentation.sendStatus(0, mResult);
-    }
-
-    /**
-     * Compile the app package using compilerFilter and return true or false
-     * based on status of the compilation command.
-     */
-    private boolean compileApp(String compilerFilter, String appPkgName) throws IOException {
-        try (ParcelFileDescriptor result = getInstrumentation().getUiAutomation().
-                executeShellCommand(String.format(COMPILE_CMD, compilerFilter, appPkgName));
-                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
-                        new FileInputStream(result.getFileDescriptor())))) {
-            String line;
-            while ((line = bufferedReader.readLine()) != null) {
-                if (line.contains(COMPILE_SUCCESS)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-
-    /**
-     * If launch order is "cyclic" then apps will be launched one after the
-     * other for each iteration count.
-     * If launch order is "sequential" then each app will be launched for given number
-     * iterations at once before launching the other apps.
-     */
-    private void setLaunchOrder() {
-        if (LAUNCH_ORDER_CYCLIC.equalsIgnoreCase(mLaunchOrder)) {
-            for (String compilerFilter : mCompilerFilters) {
-                if (mTrialLaunch) {
-                    for (String app : mNameToResultKey.keySet()) {
-                        mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, TRIAL_LAUNCH));
-                    }
-                }
-                for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) {
-                    for (String app : mNameToResultKey.keySet()) {
-                        mLaunchOrderList.add(new LaunchOrder(app, compilerFilter,
-                                  String.format(LAUNCH_ITERATION, launchCount)));
-                    }
-                }
-                if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) {
-                    for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) {
-                        for (String app : mNameToResultKey.keySet()) {
-                            mLaunchOrderList.add(new LaunchOrder(app, compilerFilter,
-                                      String.format(TRACE_ITERATION, traceCount)));
-                        }
-                    }
-                }
-            }
-        } else if (LAUNCH_ORDER_SEQUENTIAL.equalsIgnoreCase(mLaunchOrder)) {
-            for (String compilerFilter : mCompilerFilters) {
-                for (String app : mNameToResultKey.keySet()) {
-                    if (mTrialLaunch) {
-                        mLaunchOrderList.add(new LaunchOrder(app, compilerFilter, TRIAL_LAUNCH));
-                    }
-                    for (int launchCount = 0; launchCount < mLaunchIterations; launchCount++) {
-                        mLaunchOrderList.add(new LaunchOrder(app, compilerFilter,
-                                String.format(LAUNCH_ITERATION, launchCount)));
-                    }
-                    if (mTraceDirectoryStr != null && !mTraceDirectoryStr.isEmpty()) {
-                        for (int traceCount = 0; traceCount < mTraceLaunchCount; traceCount++) {
-                            mLaunchOrderList.add(new LaunchOrder(app, compilerFilter,
-                                    String.format(TRACE_ITERATION, traceCount)));
-                        }
-                    }
-                }
-            }
-        } else {
-            assertTrue("Launch order is not valid parameter", false);
-        }
-    }
-
-    private void dropCache() {
-        if (mDropCache) {
-            assertNotNull("Issue in dropping the cache",
-                    getInstrumentation().getUiAutomation()
-                            .executeShellCommand(DROP_CACHE_SCRIPT));
-        }
-    }
-
-    private void parseArgs(Bundle args) {
-        mNameToResultKey = new LinkedHashMap<String, String>();
-        mNameToLaunchTime = new HashMap<>();
-        String launchIterations = args.getString(KEY_LAUNCH_ITERATIONS);
-        if (launchIterations != null) {
-            mLaunchIterations = Integer.parseInt(launchIterations);
-        }
-        String appList = args.getString(KEY_APPS);
-        if (appList == null)
-            return;
-
-        String appNames[] = appList.split("\\|");
-        for (String pair : appNames) {
-            String[] parts = pair.split("\\^");
-            if (parts.length != 2) {
-                Log.e(TAG, "The apps key is incorrectly formatted");
-                fail();
-            }
-
-            mNameToResultKey.put(parts[0], parts[1]);
-            mNameToLaunchTime.put(parts[0], null);
-        }
-        String requiredAccounts = args.getString(KEY_REQUIRED_ACCOUNTS);
-        if (requiredAccounts != null) {
-            mRequiredAccounts = new HashSet<String>();
-            for (String accountType : requiredAccounts.split(",")) {
-                mRequiredAccounts.add(accountType);
-            }
-        }
-
-        String compilerFilterList = args.getString(KEY_COMPILER_FILTERS);
-        if (compilerFilterList != null) {
-            // If a compiler filter is passed, we make a trial launch to force compilation
-            // of the apps.
-            mTrialLaunch = true;
-            mCompilerFilters = compilerFilterList.split("\\|");
-        } else {
-            // Just pass a null compiler filter to use the current state of the app.
-            mCompilerFilters = new String[1];
-        }
-
-        // Pre-populate the results map to avoid null checks.
-        for (String app : mNameToLaunchTime.keySet()) {
-            HashMap<String, List<AppLaunchResult>> map = new HashMap<>();
-            mNameToLaunchTime.put(app, map);
-            for (String compilerFilter : mCompilerFilters) {
-                map.put(compilerFilter, new ArrayList<>());
-            }
-        }
-
-        mTraceDirectoryStr = args.getString(KEY_TRACE_DIRECTORY);
-        mDropCache = Boolean.parseBoolean(args.getString(KEY_DROP_CACHE));
-        mSimplePerfCmd = args.getString(KEY_SIMPLEPERF_CMD);
-        mLaunchOrder = args.getString(KEY_LAUNCH_ORDER, LAUNCH_ORDER_CYCLIC);
-        mSimplePerfAppOnly = Boolean.parseBoolean(args.getString(KEY_SIMPLEPERF_APP));
-        mTrialLaunch = mTrialLaunch || Boolean.parseBoolean(args.getString(KEY_TRIAL_LAUNCH));
-
-        if (mSimplePerfCmd != null && mSimplePerfAppOnly) {
-            Log.w(TAG, String.format("Passing both %s and %s is not supported, ignoring %s",
-                KEY_SIMPLEPERF_CMD, KEY_SIMPLEPERF_APP));
-        }
-    }
-
-    private boolean hasLeanback(Context context) {
-        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
-    }
-
-    private void createMappings() {
-        mNameToIntent = new LinkedHashMap<String, Intent>();
-
-        PackageManager pm = getInstrumentation().getContext()
-                .getPackageManager();
-        Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
-        intentToResolve.addCategory(hasLeanback(getInstrumentation().getContext()) ?
-                Intent.CATEGORY_LEANBACK_LAUNCHER :
-                Intent.CATEGORY_LAUNCHER);
-        List<ResolveInfo> ris = pm.queryIntentActivities(intentToResolve, 0);
-        resolveLoop(ris, intentToResolve, pm);
-        // For Wear
-        intentToResolve = new Intent(WEARABLE_ACTION_GOOGLE);
-        ris = pm.queryIntentActivities(intentToResolve, 0);
-        resolveLoop(ris, intentToResolve, pm);
-    }
-
-    private void resolveLoop(List<ResolveInfo> ris, Intent intentToResolve, PackageManager pm) {
-        if (ris == null || ris.isEmpty()) {
-            Log.i(TAG, "Could not find any apps");
-        } else {
-            for (ResolveInfo ri : ris) {
-                Intent startIntent = new Intent(intentToResolve);
-                startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                        | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-                startIntent.setClassName(ri.activityInfo.packageName,
-                        ri.activityInfo.name);
-                String appName = ri.loadLabel(pm).toString();
-                if (appName != null) {
-                    // Support launching intent using package name or app name
-                    mNameToIntent.put(ri.activityInfo.packageName, startIntent);
-                    mNameToIntent.put(appName, startIntent);
-                }
-            }
-        }
-    }
-
-    private AppLaunchResult startApp(String appName, boolean forceStopBeforeLaunch,
-            String launchReason) throws NameNotFoundException, RemoteException {
-        Log.i(TAG, "Starting " + appName);
-
-        Intent startIntent = mNameToIntent.get(appName);
-        if (startIntent == null) {
-            Log.w(TAG, "App does not exist: " + appName);
-            mResult.putString(mNameToResultKey.get(appName), "App does not exist");
-            return new AppLaunchResult();
-        }
-        AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent, forceStopBeforeLaunch,
-                launchReason);
-        Thread t = new Thread(runnable);
-        t.start();
-        try {
-            t.join(JOIN_TIMEOUT);
-        } catch (InterruptedException e) {
-            // ignore
-        }
-        return runnable.getResult();
-    }
-
-    private void checkAccountSignIn() {
-        // ensure that the device has the required account types before starting test
-        // e.g. device must have a valid Google account sign in to measure a meaningful launch time
-        // for Gmail
-        if (mRequiredAccounts == null || mRequiredAccounts.isEmpty()) {
-            return;
-        }
-        final AccountManager am =
-                (AccountManager) getInstrumentation().getTargetContext().getSystemService(
-                        Context.ACCOUNT_SERVICE);
-        Account[] accounts = am.getAccounts();
-        // use set here in case device has multiple accounts of the same type
-        Set<String> foundAccounts = new HashSet<String>();
-        for (Account account : accounts) {
-            if (mRequiredAccounts.contains(account.type)) {
-                foundAccounts.add(account.type);
-            }
-        }
-        // check if account type matches, if not, fail test with message on what account types
-        // are missing
-        if (mRequiredAccounts.size() != foundAccounts.size()) {
-            mRequiredAccounts.removeAll(foundAccounts);
-            StringBuilder sb = new StringBuilder("Device missing these accounts:");
-            for (String account : mRequiredAccounts) {
-                sb.append(' ');
-                sb.append(account);
-            }
-            fail(sb.toString());
-        }
-    }
-
-    private void closeApp(String appName, boolean forceStopApp) {
-        Intent homeIntent = new Intent(Intent.ACTION_MAIN);
-        homeIntent.addCategory(Intent.CATEGORY_HOME);
-        homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-        getInstrumentation().getContext().startActivity(homeIntent);
-        sleep(POST_LAUNCH_IDLE_TIMEOUT);
-        if (forceStopApp) {
-            Intent startIntent = mNameToIntent.get(appName);
-            if (startIntent != null) {
-                String packageName = startIntent.getComponent().getPackageName();
-                try {
-                    mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Error closing app", e);
-                }
-            }
-        }
-    }
-
-    private void sleep(int time) {
-        try {
-            Thread.sleep(time);
-        } catch (InterruptedException e) {
-            // ignore
-        }
-    }
-
-    private void reportError(String appName, String processName) {
-        ActivityManager am = (ActivityManager) getInstrumentation()
-                .getContext().getSystemService(Context.ACTIVITY_SERVICE);
-        List<ProcessErrorStateInfo> crashes = am.getProcessesInErrorState();
-        if (crashes != null) {
-            for (ProcessErrorStateInfo crash : crashes) {
-                if (!crash.processName.equals(processName))
-                    continue;
-
-                Log.w(TAG, appName + " crashed: " + crash.shortMsg);
-                mResult.putString(mNameToResultKey.get(appName), crash.shortMsg);
-                return;
-            }
-        }
-
-        mResult.putString(mNameToResultKey.get(appName),
-                "Crashed for unknown reason");
-        Log.w(TAG, appName
-                + " not found in process list, most likely it is crashed");
-    }
-
-    private class LaunchOrder {
-        private String mApp;
-        private String mCompilerFilter;
-        private String mLaunchReason;
-
-        LaunchOrder(String app, String compilerFilter, String launchReason){
-            mApp = app;
-            mCompilerFilter = compilerFilter;
-            mLaunchReason = launchReason;
-        }
-
-        public String getApp() {
-            return mApp;
-        }
-
-        public void setApp(String app) {
-            mApp = app;
-        }
-
-        public String getCompilerFilter() {
-            return mCompilerFilter;
-        }
-
-        public String getLaunchReason() {
-            return mLaunchReason;
-        }
-
-        public void setLaunchReason(String launchReason) {
-            mLaunchReason = launchReason;
-        }
-    }
-
-    private class AppLaunchResult {
-        long mLaunchTime;
-        long mCpuCycles;
-        long mMajorFaults;
-
-        AppLaunchResult() {
-            mLaunchTime = -1L;
-            mCpuCycles = -1L;
-            mMajorFaults = -1L;
-        }
-
-        AppLaunchResult(String launchTime, String cpuCycles, String majorFaults) {
-            try {
-                mLaunchTime = Long.parseLong(launchTime, 10);
-                mCpuCycles = Long.parseLong(cpuCycles, 10);
-                mMajorFaults = Long.parseLong(majorFaults, 10);
-            } catch (NumberFormatException e) {
-                Log.e(TAG, "Error parsing result", e);
-            }
-        }
-    }
-
-    private class AppLaunchRunnable implements Runnable {
-        private Intent mLaunchIntent;
-        private AppLaunchResult mLaunchResult;
-        private boolean mForceStopBeforeLaunch;
-        private String mLaunchReason;
-
-        public AppLaunchRunnable(Intent intent, boolean forceStopBeforeLaunch,
-                String launchReason) {
-            mLaunchIntent = intent;
-            mForceStopBeforeLaunch = forceStopBeforeLaunch;
-            mLaunchReason = launchReason;
-            mLaunchResult = new AppLaunchResult();
-        }
-
-        public AppLaunchResult getResult() {
-            return mLaunchResult;
-        }
-
-        public void run() {
-            File launchFile = null;
-            try {
-                String packageName = mLaunchIntent.getComponent().getPackageName();
-                String componentName = mLaunchIntent.getComponent().flattenToShortString();
-                if (mForceStopBeforeLaunch) {
-                    Log.v(TAG, "Stopping app before launch");
-                    mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
-                } else {
-                    Log.v(TAG, "Not killing app. Going to Home Screen.");
-                    ParcelFileDescriptor goHome = getInstrumentation().getUiAutomation()
-                        .executeShellCommand("input keyevent 3");
-                }
-                String launchCmd = String.format("%s %s", APP_LAUNCH_CMD, componentName);
-                if (mSimplePerfAppOnly) {
-                    try {
-                        // executeShellCommand cannot handle shell specific actions, like '&'.
-                        // Therefore, we create a file containing the command and make that
-                        // the command to launch.
-                        launchFile = File.createTempFile(LAUNCH_SCRIPT_NAME, ".sh");
-                        launchFile.setExecutable(true);
-                        try (FileOutputStream stream = new FileOutputStream(launchFile);
-                             BufferedWriter writer =
-                                new BufferedWriter(new OutputStreamWriter(stream))) {
-                            String cmd = String.format(SIMPLEPERF_APP_CMD, packageName, launchCmd);
-                            writer.write(cmd);
-                        }
-                        launchCmd = launchFile.getAbsolutePath();
-                    } catch (IOException e) {
-                        Log.w(TAG, "Error writing the launch command", e);
-                        return;
-                    }
-                } else if (null != mSimplePerfCmd) {
-                    launchCmd = String.format("%s %s", mSimplePerfCmd, launchCmd);
-                }
-                Log.v(TAG, "Final launch cmd:" + launchCmd);
-                ParcelFileDescriptor parcelDesc = getInstrumentation().getUiAutomation()
-                        .executeShellCommand(launchCmd);
-                mLaunchResult = parseLaunchTimeAndWrite(parcelDesc, String.format
-                        ("App Launch :%s %s", componentName, mLaunchReason));
-            } catch (RemoteException e) {
-                Log.w(TAG, "Error launching app", e);
-            } finally {
-                if (launchFile != null) {
-                    launchFile.delete();
-                }
-            }
-        }
-
-        /**
-         * Method to parse the launch time info and write the result to file
-         *
-         * @param parcelDesc
-         * @return
-         */
-        private AppLaunchResult parseLaunchTimeAndWrite(ParcelFileDescriptor parcelDesc,
-                String headerInfo) {
-            String launchTime = "-1";
-            String cpuCycles = "-1";
-            String majorFaults = "-1";
-            boolean launchSuccess = false;
-            try {
-                InputStream inputStream = new FileInputStream(parcelDesc.getFileDescriptor());
-                /* SAMPLE OUTPUT :
-                Starting: Intent { cmp=com.google.android.calculator/com.android.calculator2.Calculator }
-                Status: ok
-                Activity: com.google.android.calculator/com.android.calculator2.Calculator
-                ThisTime: 357
-                TotalTime: 357
-                WaitTime: 377
-                Complete*/
-                /* WHEN NOT KILLING HOME :
-                Starting: Intent { cmp=com.google.android.wearable.app/
-                    com.google.android.clockwork.home.calendar.AgendaActivity }
-                Warning: Activity not started, its current task has been brought to the front
-                Status: ok
-                Activity: com.google.android.wearable.app/
-                    com.google.android.clockwork.home.calendar.AgendaActivity
-                ThisTime: 209
-                TotalTime: 209
-                WaitTime: 285
-                Complete*/
-                /* WITH SIMPLEPERF :
-                Performance counter statistics,
-                6595722690,cpu-cycles,4.511040,GHz,(100%),
-                0,major-faults,0.000,/sec,(100%),
-                Total test time,1.462129,seconds,*/
-                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
-                        inputStream));
-                String line = null;
-                int lineCount = 1;
-                int addLineForWarning = 0;
-                mBufferedWriter.newLine();
-                mBufferedWriter.write(headerInfo);
-                mBufferedWriter.newLine();
-                while ((line = bufferedReader.readLine()) != null) {
-                    if (lineCount == 2 && line.contains(WARNING_MESSAGE)) {
-                        addLineForWarning = 1;
-                    }
-                    if (lineCount == (2 + addLineForWarning) && line.contains(SUCCESS_MESSAGE)) {
-                        launchSuccess = true;
-                    }
-                    // Parse TotalTime which is the launch time
-                    if (launchSuccess && lineCount == (5 + addLineForWarning)) {
-                        String launchSplit[] = line.split(":");
-                        launchTime = launchSplit[1].trim();
-                    }
-
-                    if (mSimplePerfAppOnly) {
-                        // Parse simpleperf output.
-                        if (lineCount == (9 + addLineForWarning)) {
-                            if (!line.contains("cpu-cycles")) {
-                                Log.e(TAG, "Error in simpleperf output");
-                            } else {
-                                cpuCycles = line.split(",")[0].trim();
-                            }
-                        } else if (lineCount == (10 + addLineForWarning)) {
-                            if (!line.contains("major-faults")) {
-                                Log.e(TAG, "Error in simpleperf output");
-                            } else {
-                                majorFaults = line.split(",")[0].trim();
-                            }
-                        }
-                    }
-                    mBufferedWriter.write(line);
-                    mBufferedWriter.newLine();
-                    lineCount++;
-                }
-                mBufferedWriter.flush();
-                inputStream.close();
-            } catch (IOException e) {
-                Log.w(TAG, "Error writing the launch file", e);
-            }
-            return new AppLaunchResult(launchTime, cpuCycles, majorFaults);
-        }
-
-    }
-}
diff --git a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java
index d03aee2..4a724b7 100644
--- a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java
@@ -15,12 +15,13 @@
  */
 package android.net.vcn;
 
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 
 import org.junit.Test;
 
@@ -32,26 +33,26 @@
     private static final Set<Integer> ALLOWED_CARRIER_IDS = new HashSet<>();
 
     // Package private for use in VcnGatewayConnectionConfigTest
-    static VcnCellUnderlyingNetworkTemplate getTestNetworkPriority() {
+    static VcnCellUnderlyingNetworkTemplate getTestNetworkTemplate() {
         return new VcnCellUnderlyingNetworkTemplate.Builder()
                 .setNetworkQuality(NETWORK_QUALITY_OK)
-                .setAllowMetered(true /* allowMetered */)
-                .setAllowedOperatorPlmnIds(ALLOWED_PLMN_IDS)
-                .setAllowedSpecificCarrierIds(ALLOWED_CARRIER_IDS)
-                .setAllowRoaming(true /* allowRoaming */)
-                .setRequireOpportunistic(true /* requireOpportunistic */)
+                .setMetered(MATCH_FORBIDDEN)
+                .setOperatorPlmnIds(ALLOWED_PLMN_IDS)
+                .setSimSpecificCarrierIds(ALLOWED_CARRIER_IDS)
+                .setRoaming(MATCH_FORBIDDEN)
+                .setOpportunistic(MATCH_REQUIRED)
                 .build();
     }
 
     @Test
     public void testBuilderAndGetters() {
-        final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority();
+        final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate();
         assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality());
-        assertTrue(networkPriority.allowMetered());
-        assertEquals(ALLOWED_PLMN_IDS, networkPriority.getAllowedOperatorPlmnIds());
-        assertEquals(ALLOWED_CARRIER_IDS, networkPriority.getAllowedSpecificCarrierIds());
-        assertTrue(networkPriority.allowRoaming());
-        assertTrue(networkPriority.requireOpportunistic());
+        assertEquals(MATCH_FORBIDDEN, networkPriority.getMetered());
+        assertEquals(ALLOWED_PLMN_IDS, networkPriority.getOperatorPlmnIds());
+        assertEquals(ALLOWED_CARRIER_IDS, networkPriority.getSimSpecificCarrierIds());
+        assertEquals(MATCH_FORBIDDEN, networkPriority.getRoaming());
+        assertEquals(MATCH_REQUIRED, networkPriority.getOpportunistic());
     }
 
     @Test
@@ -59,16 +60,16 @@
         final VcnCellUnderlyingNetworkTemplate networkPriority =
                 new VcnCellUnderlyingNetworkTemplate.Builder().build();
         assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality());
-        assertFalse(networkPriority.allowMetered());
-        assertEquals(new HashSet<String>(), networkPriority.getAllowedOperatorPlmnIds());
-        assertEquals(new HashSet<Integer>(), networkPriority.getAllowedSpecificCarrierIds());
-        assertFalse(networkPriority.allowRoaming());
-        assertFalse(networkPriority.requireOpportunistic());
+        assertEquals(MATCH_ANY, networkPriority.getMetered());
+        assertEquals(new HashSet<String>(), networkPriority.getOperatorPlmnIds());
+        assertEquals(new HashSet<Integer>(), networkPriority.getSimSpecificCarrierIds());
+        assertEquals(MATCH_ANY, networkPriority.getRoaming());
+        assertEquals(MATCH_ANY, networkPriority.getOpportunistic());
     }
 
     @Test
     public void testPersistableBundle() {
-        final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority();
+        final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate();
         assertEquals(
                 networkPriority,
                 VcnUnderlyingNetworkTemplate.fromPersistableBundle(
diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
index 1f2905d..2aef9ae 100644
--- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
@@ -17,8 +17,8 @@
 package android.net.vcn;
 
 import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
-import static android.net.vcn.VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_PRIORITIES;
-import static android.net.vcn.VcnGatewayConnectionConfig.UNDERLYING_NETWORK_PRIORITIES_KEY;
+import static android.net.vcn.VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES;
+import static android.net.vcn.VcnGatewayConnectionConfig.UNDERLYING_NETWORK_TEMPLATES_KEY;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -40,8 +40,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
@@ -54,17 +55,17 @@
             };
     public static final int[] UNDERLYING_CAPS = new int[] {NetworkCapabilities.NET_CAPABILITY_DUN};
 
-    private static final LinkedHashSet<VcnUnderlyingNetworkTemplate> UNDERLYING_NETWORK_PRIORITIES =
-            new LinkedHashSet();
+    private static final List<VcnUnderlyingNetworkTemplate> UNDERLYING_NETWORK_TEMPLATES =
+            new ArrayList();
 
     static {
         Arrays.sort(EXPOSED_CAPS);
         Arrays.sort(UNDERLYING_CAPS);
 
-        UNDERLYING_NETWORK_PRIORITIES.add(
-                VcnCellUnderlyingNetworkTemplateTest.getTestNetworkPriority());
-        UNDERLYING_NETWORK_PRIORITIES.add(
-                VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkPriority());
+        UNDERLYING_NETWORK_TEMPLATES.add(
+                VcnCellUnderlyingNetworkTemplateTest.getTestNetworkTemplate());
+        UNDERLYING_NETWORK_TEMPLATES.add(
+                VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkTemplate());
     }
 
     public static final long[] RETRY_INTERVALS_MS =
@@ -95,7 +96,7 @@
     // Public for use in VcnGatewayConnectionTest
     public static VcnGatewayConnectionConfig buildTestConfig() {
         final VcnGatewayConnectionConfig.Builder builder =
-                newBuilder().setVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_PRIORITIES);
+                newBuilder().setVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_TEMPLATES);
 
         return buildTestConfigWithExposedCaps(builder, EXPOSED_CAPS);
     }
@@ -174,10 +175,10 @@
     }
 
     @Test
-    public void testBuilderRequiresNonNullNetworkPriorities() {
+    public void testBuilderRequiresNonNullNetworkTemplates() {
         try {
             newBuilder().setVcnUnderlyingNetworkPriorities(null);
-            fail("Expected exception due to invalid underlyingNetworkPriorities");
+            fail("Expected exception due to invalid underlyingNetworkTemplates");
         } catch (NullPointerException e) {
         }
     }
@@ -219,7 +220,7 @@
         Arrays.sort(exposedCaps);
         assertArrayEquals(EXPOSED_CAPS, exposedCaps);
 
-        assertEquals(UNDERLYING_NETWORK_PRIORITIES, config.getVcnUnderlyingNetworkPriorities());
+        assertEquals(UNDERLYING_NETWORK_TEMPLATES, config.getVcnUnderlyingNetworkPriorities());
         assertEquals(TUNNEL_CONNECTION_PARAMS, config.getTunnelConnectionParams());
 
         assertArrayEquals(RETRY_INTERVALS_MS, config.getRetryIntervalsMillis());
@@ -234,13 +235,13 @@
     }
 
     @Test
-    public void testParsePersistableBundleWithoutVcnUnderlyingNetworkPriorities() {
+    public void testParsePersistableBundleWithoutVcnUnderlyingNetworkTemplates() {
         PersistableBundle configBundle = buildTestConfig().toPersistableBundle();
-        configBundle.putPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY, null);
+        configBundle.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, null);
 
         final VcnGatewayConnectionConfig config = new VcnGatewayConnectionConfig(configBundle);
         assertEquals(
-                DEFAULT_UNDERLYING_NETWORK_PRIORITIES, config.getVcnUnderlyingNetworkPriorities());
+                DEFAULT_UNDERLYING_NETWORK_TEMPLATES, config.getVcnUnderlyingNetworkPriorities());
     }
 
     private static IkeTunnelConnectionParams buildTunnelConnectionParams(String ikePsk) {
@@ -285,39 +286,36 @@
         assertNotEquals(config, anotherConfig);
     }
 
-    private static VcnGatewayConnectionConfig buildTestConfigWithVcnUnderlyingNetworkPriorities(
-            LinkedHashSet<VcnUnderlyingNetworkTemplate> networkPriorities) {
+    private static VcnGatewayConnectionConfig buildTestConfigWithVcnUnderlyingNetworkTemplates(
+            List<VcnUnderlyingNetworkTemplate> networkTemplates) {
         return buildTestConfigWithExposedCaps(
                 new VcnGatewayConnectionConfig.Builder(
-                                "buildTestConfigWithVcnUnderlyingNetworkPriorities",
+                                "buildTestConfigWithVcnUnderlyingNetworkTemplates",
                                 TUNNEL_CONNECTION_PARAMS)
-                        .setVcnUnderlyingNetworkPriorities(networkPriorities),
+                        .setVcnUnderlyingNetworkPriorities(networkTemplates),
                 EXPOSED_CAPS);
     }
 
     @Test
-    public void testVcnUnderlyingNetworkPrioritiesEquality() throws Exception {
+    public void testVcnUnderlyingNetworkTemplatesEquality() throws Exception {
         final VcnGatewayConnectionConfig config =
-                buildTestConfigWithVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_PRIORITIES);
+                buildTestConfigWithVcnUnderlyingNetworkTemplates(UNDERLYING_NETWORK_TEMPLATES);
 
-        final LinkedHashSet<VcnUnderlyingNetworkTemplate> networkPrioritiesEqual =
-                new LinkedHashSet();
-        networkPrioritiesEqual.add(VcnCellUnderlyingNetworkTemplateTest.getTestNetworkPriority());
-        networkPrioritiesEqual.add(VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkPriority());
+        final List<VcnUnderlyingNetworkTemplate> networkTemplatesEqual = new ArrayList();
+        networkTemplatesEqual.add(VcnCellUnderlyingNetworkTemplateTest.getTestNetworkTemplate());
+        networkTemplatesEqual.add(VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkTemplate());
         final VcnGatewayConnectionConfig configEqual =
-                buildTestConfigWithVcnUnderlyingNetworkPriorities(networkPrioritiesEqual);
+                buildTestConfigWithVcnUnderlyingNetworkTemplates(networkTemplatesEqual);
 
-        final LinkedHashSet<VcnUnderlyingNetworkTemplate> networkPrioritiesNotEqual =
-                new LinkedHashSet();
-        networkPrioritiesNotEqual.add(
-                VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkPriority());
+        final List<VcnUnderlyingNetworkTemplate> networkTemplatesNotEqual = new ArrayList();
+        networkTemplatesNotEqual.add(VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkTemplate());
         final VcnGatewayConnectionConfig configNotEqual =
-                buildTestConfigWithVcnUnderlyingNetworkPriorities(networkPrioritiesNotEqual);
+                buildTestConfigWithVcnUnderlyingNetworkTemplates(networkTemplatesNotEqual);
 
-        assertEquals(UNDERLYING_NETWORK_PRIORITIES, networkPrioritiesEqual);
+        assertEquals(UNDERLYING_NETWORK_TEMPLATES, networkTemplatesEqual);
         assertEquals(config, configEqual);
 
-        assertNotEquals(UNDERLYING_NETWORK_PRIORITIES, networkPrioritiesNotEqual);
+        assertNotEquals(UNDERLYING_NETWORK_TEMPLATES, networkTemplatesNotEqual);
         assertNotEquals(config, configNotEqual);
     }
 }
diff --git a/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java
index 652057f..cb5b47b 100644
--- a/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java
@@ -15,36 +15,38 @@
  */
 package android.net.vcn;
 
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import org.junit.Test;
 
+import java.util.Set;
+
 public class VcnWifiUnderlyingNetworkTemplateTest {
     private static final String SSID = "TestWifi";
     private static final int INVALID_NETWORK_QUALITY = -1;
 
     // Package private for use in VcnGatewayConnectionConfigTest
-    static VcnWifiUnderlyingNetworkTemplate getTestNetworkPriority() {
+    static VcnWifiUnderlyingNetworkTemplate getTestNetworkTemplate() {
         return new VcnWifiUnderlyingNetworkTemplate.Builder()
                 .setNetworkQuality(NETWORK_QUALITY_OK)
-                .setAllowMetered(true /* allowMetered */)
-                .setSsid(SSID)
+                .setMetered(MATCH_FORBIDDEN)
+                .setSsids(Set.of(SSID))
                 .build();
     }
 
     @Test
     public void testBuilderAndGetters() {
-        final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority();
+        final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate();
         assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality());
-        assertTrue(networkPriority.allowMetered());
-        assertEquals(SSID, networkPriority.getSsid());
+        assertEquals(MATCH_FORBIDDEN, networkPriority.getMetered());
+        assertEquals(Set.of(SSID), networkPriority.getSsids());
     }
 
     @Test
@@ -52,8 +54,8 @@
         final VcnWifiUnderlyingNetworkTemplate networkPriority =
                 new VcnWifiUnderlyingNetworkTemplate.Builder().build();
         assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality());
-        assertFalse(networkPriority.allowMetered());
-        assertNull(SSID, networkPriority.getSsid());
+        assertEquals(MATCH_ANY, networkPriority.getMetered());
+        assertTrue(networkPriority.getSsids().isEmpty());
     }
 
     @Test
@@ -68,7 +70,7 @@
 
     @Test
     public void testPersistableBundle() {
-        final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority();
+        final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate();
         assertEquals(
                 networkPriority,
                 VcnUnderlyingNetworkTemplate.fromPersistableBundle(
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
index f23d5bf..4bb7de8 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.vcn.routeselection;
 
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK;
 
 import static com.android.server.vcn.VcnTestUtils.setupSystemService;
@@ -145,7 +147,7 @@
         final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
                 new VcnWifiUnderlyingNetworkTemplate.Builder()
                         .setNetworkQuality(NETWORK_QUALITY_OK)
-                        .setAllowMetered(false /* allowMetered */)
+                        .setMetered(MATCH_FORBIDDEN)
                         .build();
 
         assertFalse(
@@ -164,7 +166,6 @@
         final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
                 new VcnWifiUnderlyingNetworkTemplate.Builder()
                         .setNetworkQuality(NETWORK_QUALITY_OK)
-                        .setAllowMetered(true /* allowMetered */)
                         .build();
         final UnderlyingNetworkRecord selectedNetworkRecord =
                 isSelectedNetwork ? mWifiNetworkRecord : null;
@@ -214,8 +215,7 @@
         final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
                 new VcnWifiUnderlyingNetworkTemplate.Builder()
                         .setNetworkQuality(NETWORK_QUALITY_OK)
-                        .setAllowMetered(true /* allowMetered */)
-                        .setSsid(nwPrioritySsid)
+                        .setSsids(Set.of(nwPrioritySsid))
                         .build();
 
         assertEquals(
@@ -238,10 +238,7 @@
     }
 
     private static VcnCellUnderlyingNetworkTemplate.Builder getCellNetworkPriorityBuilder() {
-        return new VcnCellUnderlyingNetworkTemplate.Builder()
-                .setNetworkQuality(NETWORK_QUALITY_OK)
-                .setAllowMetered(true /* allowMetered */)
-                .setAllowRoaming(true /* allowRoaming */);
+        return new VcnCellUnderlyingNetworkTemplate.Builder().setNetworkQuality(NETWORK_QUALITY_OK);
     }
 
     @Test
@@ -258,9 +255,7 @@
     @Test
     public void testMatchOpportunisticCell() {
         final VcnCellUnderlyingNetworkTemplate opportunisticCellNetworkPriority =
-                getCellNetworkPriorityBuilder()
-                        .setRequireOpportunistic(true /* requireOpportunistic */)
-                        .build();
+                getCellNetworkPriorityBuilder().setOpportunistic(MATCH_REQUIRED).build();
 
         when(mSubscriptionSnapshot.isOpportunistic(SUB_ID)).thenReturn(true);
         when(mSubscriptionSnapshot.getAllSubIdsInGroup(SUB_GROUP)).thenReturn(new ArraySet<>());
@@ -279,7 +274,7 @@
         final String networkPriorityPlmnId = useMatchedPlmnId ? PLMN_ID : PLMN_ID_OTHER;
         final VcnCellUnderlyingNetworkTemplate networkPriority =
                 getCellNetworkPriorityBuilder()
-                        .setAllowedOperatorPlmnIds(Set.of(networkPriorityPlmnId))
+                        .setOperatorPlmnIds(Set.of(networkPriorityPlmnId))
                         .build();
 
         assertEquals(
@@ -308,7 +303,7 @@
         final int networkPriorityCarrierId = useMatchedCarrierId ? CARRIER_ID : CARRIER_ID_OTHER;
         final VcnCellUnderlyingNetworkTemplate networkPriority =
                 getCellNetworkPriorityBuilder()
-                        .setAllowedSpecificCarrierIds(Set.of(networkPriorityCarrierId))
+                        .setSimSpecificCarrierIds(Set.of(networkPriorityCarrierId))
                         .build();
 
         assertEquals(
@@ -336,7 +331,7 @@
     @Test
     public void testMatchWifiFailWithoutNotRoamingBit() {
         final VcnCellUnderlyingNetworkTemplate networkPriority =
-                getCellNetworkPriorityBuilder().setAllowRoaming(false /* allowRoaming */).build();
+                getCellNetworkPriorityBuilder().setRoaming(MATCH_FORBIDDEN).build();
 
         assertFalse(
                 checkMatchesCellPriorityRule(
@@ -353,7 +348,7 @@
                 calculatePriorityClass(
                         mVcnContext,
                         networkRecord,
-                        VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_PRIORITIES,
+                        VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
                         SUB_GROUP,
                         mSubscriptionSnapshot,
                         null /* currentlySelected */,
diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp
index 8975713..fbfbf68 100644
--- a/tools/aapt2/cmd/Compile_test.cpp
+++ b/tools/aapt2/cmd/Compile_test.cpp
@@ -19,12 +19,11 @@
 #include "android-base/file.h"
 #include "android-base/stringprintf.h"
 #include "android-base/utf8.h"
-
+#include "format/proto/ProtoDeserialize.h"
 #include "io/StringStream.h"
 #include "io/ZipArchive.h"
 #include "java/AnnotationProcessor.h"
 #include "test/Test.h"
-#include "format/proto/ProtoDeserialize.h"
 
 namespace aapt {
 
@@ -59,55 +58,56 @@
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
   const std::string kResDir = BuildPath({android::base::Dirname(android::base::GetExecutablePath()),
                                          "integration-tests", "CompileTest", "res"});
+  const std::string kOutDir = testing::TempDir();
 
   // Resource files without periods in the file name should not throw errors
   const std::string path0 = BuildPath({kResDir, "values", "values.xml"});
-  const std::string path0_out = BuildPath({kResDir, "values_values.arsc.flat"});
+  const std::string path0_out = BuildPath({kOutDir, "values_values.arsc.flat"});
   ::android::base::utf8::unlink(path0_out.c_str());
-  ASSERT_EQ(TestCompile(path0, kResDir, /** legacy */ false, diag), 0);
+  ASSERT_EQ(TestCompile(path0, kOutDir, /** legacy */ false, diag), 0);
   ASSERT_EQ(::android::base::utf8::unlink(path0_out.c_str()), 0);
-  ASSERT_EQ(TestCompile(path0, kResDir, /** legacy */ true, diag), 0);
+  ASSERT_EQ(TestCompile(path0, kOutDir, /** legacy */ true, diag), 0);
   ASSERT_EQ(::android::base::utf8::unlink(path0_out.c_str()), 0);
 
   const std::string path1 = BuildPath({kResDir, "drawable", "image.png"});
-  const std::string path1_out = BuildPath({kResDir, "drawable_image.png.flat"});
+  const std::string path1_out = BuildPath({kOutDir, "drawable_image.png.flat"});
   ::android::base::utf8::unlink(path1_out.c_str());
-  ASSERT_EQ(TestCompile(path1, kResDir, /** legacy */ false, diag), 0);
+  ASSERT_EQ(TestCompile(path1, kOutDir, /** legacy */ false, diag), 0);
   ASSERT_EQ(::android::base::utf8::unlink(path1_out.c_str()), 0);
-  ASSERT_EQ(TestCompile(path1, kResDir, /** legacy */ true, diag), 0);
+  ASSERT_EQ(TestCompile(path1, kOutDir, /** legacy */ true, diag), 0);
   ASSERT_EQ(::android::base::utf8::unlink(path1_out.c_str()), 0);
 
   const std::string path2 = BuildPath({kResDir, "drawable", "image.9.png"});
-  const std::string path2_out = BuildPath({kResDir, "drawable_image.9.png.flat"});
+  const std::string path2_out = BuildPath({kOutDir, "drawable_image.9.png.flat"});
   ::android::base::utf8::unlink(path2_out.c_str());
-  ASSERT_EQ(TestCompile(path2, kResDir, /** legacy */ false, diag), 0);
+  ASSERT_EQ(TestCompile(path2, kOutDir, /** legacy */ false, diag), 0);
   ASSERT_EQ(::android::base::utf8::unlink(path2_out.c_str()), 0);
-  ASSERT_EQ(TestCompile(path2, kResDir, /** legacy */ true, diag), 0);
+  ASSERT_EQ(TestCompile(path2, kOutDir, /** legacy */ true, diag), 0);
   ASSERT_EQ(::android::base::utf8::unlink(path2_out.c_str()), 0);
 
   // Resource files with periods in the file name should fail on non-legacy compilations
   const std::string path3 = BuildPath({kResDir, "values", "values.all.xml"});
-  const std::string path3_out = BuildPath({kResDir, "values_values.all.arsc.flat"});
+  const std::string path3_out = BuildPath({kOutDir, "values_values.all.arsc.flat"});
   ::android::base::utf8::unlink(path3_out.c_str());
-  ASSERT_NE(TestCompile(path3, kResDir, /** legacy */ false, diag), 0);
+  ASSERT_NE(TestCompile(path3, kOutDir, /** legacy */ false, diag), 0);
   ASSERT_NE(::android::base::utf8::unlink(path3_out.c_str()), 0);
-  ASSERT_EQ(TestCompile(path3, kResDir, /** legacy */ true, diag), 0);
+  ASSERT_EQ(TestCompile(path3, kOutDir, /** legacy */ true, diag), 0);
   ASSERT_EQ(::android::base::utf8::unlink(path3_out.c_str()), 0);
 
   const std::string path4 = BuildPath({kResDir, "drawable", "image.small.png"});
-  const std::string path4_out = BuildPath({kResDir, "drawable_image.small.png.flat"});
+  const std::string path4_out = BuildPath({kOutDir, "drawable_image.small.png.flat"});
   ::android::base::utf8::unlink(path4_out.c_str());
-  ASSERT_NE(TestCompile(path4, kResDir, /** legacy */ false, diag), 0);
+  ASSERT_NE(TestCompile(path4, kOutDir, /** legacy */ false, diag), 0);
   ASSERT_NE(::android::base::utf8::unlink(path4_out.c_str()), 0);
-  ASSERT_EQ(TestCompile(path4, kResDir, /** legacy */ true, diag), 0);
+  ASSERT_EQ(TestCompile(path4, kOutDir, /** legacy */ true, diag), 0);
   ASSERT_EQ(::android::base::utf8::unlink(path4_out.c_str()), 0);
 
   const std::string path5 = BuildPath({kResDir, "drawable", "image.small.9.png"});
-  const std::string path5_out = BuildPath({kResDir, "drawable_image.small.9.png.flat"});
+  const std::string path5_out = BuildPath({kOutDir, "drawable_image.small.9.png.flat"});
   ::android::base::utf8::unlink(path5_out.c_str());
-  ASSERT_NE(TestCompile(path5, kResDir, /** legacy */ false, diag), 0);
+  ASSERT_NE(TestCompile(path5, kOutDir, /** legacy */ false, diag), 0);
   ASSERT_NE(::android::base::utf8::unlink(path5_out.c_str()), 0);
-  ASSERT_EQ(TestCompile(path5, kResDir, /** legacy */ true, diag), 0);
+  ASSERT_EQ(TestCompile(path5, kOutDir, /** legacy */ true, diag), 0);
   ASSERT_EQ(::android::base::utf8::unlink(path5_out.c_str()), 0);
 }
 
@@ -116,9 +116,7 @@
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
   const std::string kResDir = BuildPath({android::base::Dirname(android::base::GetExecutablePath()),
                                          "integration-tests", "CompileTest", "DirInput", "res"});
-  const std::string kOutputFlata =
-      BuildPath({android::base::Dirname(android::base::GetExecutablePath()), "integration-tests",
-                 "CompileTest", "DirInput", "compiled.flata"});
+  const std::string kOutputFlata = BuildPath({testing::TempDir(), "compiled.flata"});
   ::android::base::utf8::unlink(kOutputFlata.c_str());
 
   std::vector<android::StringPiece> args;
@@ -147,9 +145,7 @@
   const std::string kResZip =
       BuildPath({android::base::Dirname(android::base::GetExecutablePath()), "integration-tests",
                  "CompileTest", "ZipInput", "res.zip"});
-  const std::string kOutputFlata =
-      BuildPath({android::base::Dirname(android::base::GetExecutablePath()), "integration-tests",
-                 "CompileTest", "ZipInput", "compiled.flata"});
+  const std::string kOutputFlata = BuildPath({testing::TempDir(), "compiled.flata"});
 
   ::android::base::utf8::unlink(kOutputFlata.c_str());
 
@@ -257,9 +253,9 @@
 
 TEST_F(CompilerTest, RelativePathTest) {
   StdErrDiagnostics diag;
-  const std::string res_path = BuildPath(
-      {android::base::Dirname(android::base::GetExecutablePath()),
-       "integration-tests", "CompileTest", "res"});
+  const std::string res_path =
+      BuildPath({android::base::Dirname(android::base::GetExecutablePath()), "integration-tests",
+                 "CompileTest", "res"});
 
   const std::string path_values_colors = GetTestPath("values/colors.xml");
   WriteFile(path_values_colors, "<resources>"
@@ -272,9 +268,8 @@
                    "<TextBox android:id=\"@+id/text_one\" android:background=\"@color/color_one\"/>"
                    "</LinearLayout>");
 
-  const std::string compiled_files_dir  = BuildPath(
-      {android::base::Dirname(android::base::GetExecutablePath()),
-       "integration-tests", "CompileTest", "compiled"});
+  const std::string compiled_files_dir =
+      BuildPath({testing::TempDir(), "integration-tests", "CompileTest", "compiled"});
   CHECK(file::mkdirs(compiled_files_dir.data()));
 
   const std::string path_values_colors_out =
@@ -283,9 +278,8 @@
       BuildPath({compiled_files_dir, "layout_layout_one.flat"});
   ::android::base::utf8::unlink(path_values_colors_out.c_str());
   ::android::base::utf8::unlink(path_layout_layout_one_out.c_str());
-  const std::string apk_path = BuildPath(
-      {android::base::Dirname(android::base::GetExecutablePath()),
-       "integration-tests", "CompileTest", "out.apk"});
+  const std::string apk_path =
+      BuildPath({testing::TempDir(), "integration-tests", "CompileTest", "out.apk"});
 
   const std::string source_set_res = BuildPath({"main", "res"});
   const std::string relative_path_values_colors =
diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
index b939f35..4a2d0ae 100644
--- a/tools/aapt2/java/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -311,7 +311,7 @@
               component_process ? component_process->value : default_process_;
           get_name = !process.empty() && process[0] != ':';
         }
-      } else if (node->name == "instrumentation") {
+      } else if (node->name == "instrumentation" || node->name == "process") {
         get_name = true;
       }
 
diff --git a/tools/aapt2/java/ProguardRules_test.cpp b/tools/aapt2/java/ProguardRules_test.cpp
index e104066..466b7d9 100644
--- a/tools/aapt2/java/ProguardRules_test.cpp
+++ b/tools/aapt2/java/ProguardRules_test.cpp
@@ -44,6 +44,9 @@
             android:name="com.foo.BarApplication"
             android:zygotePreloadName="com.foo.BarZygotePreload"
             >
+          <processes>
+            <process android:process=":sub" android:name="com.foo.BazApplication" />
+          </processes>
           <activity android:name="com.foo.BarActivity"/>
           <service android:name="com.foo.BarService"/>
           <receiver android:name="com.foo.BarReceiver"/>
@@ -59,6 +62,7 @@
   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarAppComponentFactory { <init>(); }"));
   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarBackupAgent { <init>(); }"));
   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarApplication { <init>(); }"));
+  EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BazApplication { <init>(); }"));
   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarActivity { <init>(); }"));
   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarService { <init>(); }"));
   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarReceiver { <init>(); }"));
diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp
index e2f71dc..ddc1853 100644
--- a/tools/aapt2/test/Fixture.cpp
+++ b/tools/aapt2/test/Fixture.cpp
@@ -67,8 +67,7 @@
 }
 
 void TestDirectoryFixture::SetUp() {
-  temp_dir_ = file::BuildPath({android::base::GetExecutableDirectory(),
-                               "_temp",
+  temp_dir_ = file::BuildPath({testing::TempDir(), "_temp",
                                testing::UnitTest::GetInstance()->current_test_case()->name(),
                                testing::UnitTest::GetInstance()->current_test_info()->name()});
   ASSERT_TRUE(file::mkdirs(temp_dir_));
@@ -236,4 +235,4 @@
   return args_;
 }
 
-} // namespace aapt
\ No newline at end of file
+}  // namespace aapt
diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py
index fe2b018..3131f56 100755
--- a/tools/fonts/fontchain_linter.py
+++ b/tools/fonts/fontchain_linter.py
@@ -57,6 +57,7 @@
     'sk': 'Latn',
     'sl': 'Latn',
     'sq': 'Latn',
+    'sv': 'Latn',
     'ta': 'Taml',
     'te': 'Telu',
     'tk': 'Latn',