Merge "Move deduceRestrictedCapability to libs/net and rename it" into sc-dev
diff --git a/Android.bp b/Android.bp
index ee5e992..f47ee20 100644
--- a/Android.bp
+++ b/Android.bp
@@ -401,6 +401,13 @@
         ":platform-compat-native-aidl",
 
         // AIDL sources from external directories
+        ":android.hardware.security.keymint-V1-java-source",
+        ":android.hardware.security.secureclock-V1-java-source",
+        ":android.security.apc-java-source",
+        ":android.security.authorization-java-source",
+        ":android.security.maintenance-java-source",
+        ":android.security.vpnprofilestore-java-source",
+        ":android.system.keystore2-V1-java-source",
         ":credstore_aidl",
         ":dumpstate_aidl",
         ":framework_native_aidl",
@@ -582,13 +589,7 @@
         "android.hardware.vibrator-V1.2-java",
         "android.hardware.vibrator-V1.3-java",
         "android.hardware.vibrator-V2-java",
-        "android.security.apc-java",
-        "android.security.authorization-java",
-        "android.security.maintenance-java",
-        "android.security.vpnprofilestore-java",
-        "android.system.keystore2-V1-java",
         "android.system.suspend.control.internal-java",
-        "cameraprotosnano",
         "devicepolicyprotosnano",
 
         "com.android.sysprop.apex",
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java
index 999860f..e65abcf 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java
@@ -48,7 +48,8 @@
     @Override
     public boolean onStopJob(final JobParameters params) {
         Slog.d(TAG, "Idle maintenance job is stopped; id=" + params.getJobId()
-                + ", reason=" + JobParameters.getReasonCodeDescription(params.getStopReason()));
+                + ", reason="
+                + JobParameters.getLegacyReasonCodeDescription(params.getLegacyStopReason()));
         return false;
     }
 
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 4c8ab93..baec0c3 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1021,6 +1021,41 @@
             mJobId = jobId;
         }
 
+        /**
+         * Creates a new Builder of JobInfo from an existing instance.
+         * @hide
+         */
+        public Builder(@NonNull JobInfo job) {
+            mJobId = job.getId();
+            mJobService = job.getService();
+            mExtras = job.getExtras();
+            mTransientExtras = job.getTransientExtras();
+            mClipData = job.getClipData();
+            mClipGrantFlags = job.getClipGrantFlags();
+            mPriority = job.getPriority();
+            mFlags = job.getFlags();
+            mConstraintFlags = job.getConstraintFlags();
+            mNetworkRequest = job.getRequiredNetwork();
+            mNetworkDownloadBytes = job.getEstimatedNetworkDownloadBytes();
+            mNetworkUploadBytes = job.getEstimatedNetworkUploadBytes();
+            mTriggerContentUris = job.getTriggerContentUris() != null
+                    ? new ArrayList<>(Arrays.asList(job.getTriggerContentUris())) : null;
+            mTriggerContentUpdateDelay = job.getTriggerContentUpdateDelay();
+            mTriggerContentMaxDelay = job.getTriggerContentMaxDelay();
+            mIsPersisted = job.isPersisted();
+            mMinLatencyMillis = job.getMinLatencyMillis();
+            mMaxExecutionDelayMillis = job.getMaxExecutionDelayMillis();
+            mIsPeriodic = job.isPeriodic();
+            mHasEarlyConstraint = job.hasEarlyConstraint();
+            mHasLateConstraint = job.hasLateConstraint();
+            mIntervalMillis = job.getIntervalMillis();
+            mFlexMillis = job.getFlexMillis();
+            mInitialBackoffMillis = job.getInitialBackoffMillis();
+            // mBackoffPolicySet isn't set but it's fine since this is copying from an already valid
+            // job.
+            mBackoffPolicy = job.getBackoffPolicy();
+        }
+
         /** @hide */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         public Builder setPriority(int priority) {
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index 0d3e001..60f6475 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -16,10 +16,14 @@
 
 package android.app.job;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.usage.UsageStatsManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ClipData;
+import android.content.pm.PackageManager;
 import android.net.Network;
 import android.net.NetworkRequest;
 import android.net.Uri;
@@ -30,6 +34,9 @@
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Contains the parameters used to configure/identify your job. You do not create this object
  * yourself, instead it is handed in to your application by the System.
@@ -82,7 +89,7 @@
      */
     // TODO(142420609): make it @SystemApi for mainline
     @NonNull
-    public static String getReasonCodeDescription(int reasonCode) {
+    public static String getLegacyReasonCodeDescription(int reasonCode) {
         switch (reasonCode) {
             case REASON_CANCELED: return "canceled";
             case REASON_CONSTRAINTS_NOT_SATISFIED: return "constraints";
@@ -96,12 +103,119 @@
     }
 
     /** @hide */
-    // @SystemApi TODO make it a system api for mainline
+    // TODO: move current users of legacy reasons to new public reasons
     @NonNull
     public static int[] getJobStopReasonCodes() {
         return JOB_STOP_REASON_CODES;
     }
 
+    /**
+     * There is no reason the job is stopped. This is the value returned from the JobParameters
+     * object passed to {@link JobService#onStartJob(JobParameters)}.
+     */
+    public static final int STOP_REASON_UNDEFINED = 0;
+    /**
+     * The job was cancelled directly by the app, either by calling
+     * {@link JobScheduler#cancel(int)}, {@link JobScheduler#cancelAll()}, or by scheduling a
+     * new job with the same job ID.
+     */
+    public static final int STOP_REASON_CANCELLED_BY_APP = 1;
+    /** The job was stopped to run a higher priority job of the app. */
+    public static final int STOP_REASON_PREEMPT = 2;
+    /**
+     * The job used up its maximum execution time and timed out. Each individual job has a maximum
+     * execution time limit, regardless of how much total quota the app has. See the note on
+     * {@link JobScheduler} for the execution time limits.
+     */
+    public static final int STOP_REASON_TIMEOUT = 3;
+    /**
+     * The device state (eg. Doze, battery saver, memory usage, etc) requires JobScheduler stop this
+     * job.
+     */
+    public static final int STOP_REASON_DEVICE_STATE = 4;
+    /**
+     * The requested battery-not-low constraint is no longer satisfied.
+     *
+     * @see JobInfo.Builder#setRequiresBatteryNotLow(boolean)
+     */
+    public static final int STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW = 5;
+    /**
+     * The requested charging constraint is no longer satisfied.
+     *
+     * @see JobInfo.Builder#setRequiresCharging(boolean)
+     */
+    public static final int STOP_REASON_CONSTRAINT_CHARGING = 6;
+    /**
+     * The requested connectivity constraint is no longer satisfied.
+     *
+     * @see JobInfo.Builder#setRequiredNetwork(NetworkRequest)
+     * @see JobInfo.Builder#setRequiredNetworkType(int)
+     */
+    public static final int STOP_REASON_CONSTRAINT_CONNECTIVITY = 7;
+    /**
+     * The requested idle constraint is no longer satisfied.
+     *
+     * @see JobInfo.Builder#setRequiresDeviceIdle(boolean)
+     */
+    public static final int STOP_REASON_CONSTRAINT_DEVICE_IDLE = 8;
+    /**
+     * The requested storage-not-low constraint is no longer satisfied.
+     *
+     * @see JobInfo.Builder#setRequiresStorageNotLow(boolean)
+     */
+    public static final int STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW = 9;
+    /**
+     * The app has consumed all of its current quota. Each app is assigned a quota of how much
+     * it can run jobs within a certain time frame. The quota is informed, in part, by app standby
+     * buckets. Once an app has used up all of its quota, it won't be able to start jobs until
+     * quota is replenished, is changed, or is temporarily not applied.
+     *
+     * @see UsageStatsManager#getAppStandbyBucket()
+     */
+    public static final int STOP_REASON_QUOTA = 10;
+    /**
+     * The app is restricted from running in the background.
+     *
+     * @see ActivityManager#isBackgroundRestricted()
+     * @see PackageManager#isInstantApp()
+     */
+    public static final int STOP_REASON_BACKGROUND_RESTRICTION = 11;
+    /**
+     * The current standby bucket requires that the job stop now.
+     *
+     * @see UsageStatsManager#STANDBY_BUCKET_RESTRICTED
+     */
+    public static final int STOP_REASON_APP_STANDBY = 12;
+    /**
+     * The user stopped the job. This can happen either through force-stop, or via adb shell
+     * commands.
+     */
+    public static final int STOP_REASON_USER = 13;
+    /** The system is doing some processing that requires stopping this job. */
+    public static final int STOP_REASON_SYSTEM_PROCESSING = 14;
+
+    /** @hide */
+    @IntDef(prefix = {"STOP_REASON_"}, value = {
+            STOP_REASON_UNDEFINED,
+            STOP_REASON_CANCELLED_BY_APP,
+            STOP_REASON_PREEMPT,
+            STOP_REASON_TIMEOUT,
+            STOP_REASON_DEVICE_STATE,
+            STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW,
+            STOP_REASON_CONSTRAINT_CHARGING,
+            STOP_REASON_CONSTRAINT_CONNECTIVITY,
+            STOP_REASON_CONSTRAINT_DEVICE_IDLE,
+            STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW,
+            STOP_REASON_QUOTA,
+            STOP_REASON_BACKGROUND_RESTRICTION,
+            STOP_REASON_APP_STANDBY,
+            STOP_REASON_USER,
+            STOP_REASON_SYSTEM_PROCESSING,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StopReason {
+    }
+
     @UnsupportedAppUsage
     private final int jobId;
     private final PersistableBundle extras;
@@ -116,7 +230,8 @@
     private final String[] mTriggeredContentAuthorities;
     private final Network network;
 
-    private int stopReason; // Default value of stopReason is REASON_CANCELED
+    private int mStopReason = STOP_REASON_UNDEFINED;
+    private int mLegacyStopReason; // Default value of stopReason is REASON_CANCELED
     private String debugStopReason; // Human readable stop reason for debugging.
 
     /** @hide */
@@ -145,15 +260,23 @@
     }
 
     /**
-     * Reason onStopJob() was called on this job.
-     * @hide
+     * @return The reason {@link JobService#onStopJob(JobParameters)} was called on this job. Will
+     * be {@link #STOP_REASON_UNDEFINED} if {@link JobService#onStopJob(JobParameters)} has not
+     * yet been called.
      */
+    @StopReason
     public int getStopReason() {
-        return stopReason;
+        return mStopReason;
+    }
+
+    /** @hide */
+    public int getLegacyStopReason() {
+        return mLegacyStopReason;
     }
 
     /**
      * Reason onStopJob() was called on this job.
+     *
      * @hide
      */
     public String getDebugStopReason() {
@@ -368,13 +491,16 @@
         } else {
             network = null;
         }
-        stopReason = in.readInt();
+        mStopReason = in.readInt();
+        mLegacyStopReason = in.readInt();
         debugStopReason = in.readString();
     }
 
     /** @hide */
-    public void setStopReason(int reason, String debugStopReason) {
-        stopReason = reason;
+    public void setStopReason(@StopReason int reason, int legacyStopReason,
+            String debugStopReason) {
+        mStopReason = reason;
+        mLegacyStopReason = legacyStopReason;
         this.debugStopReason = debugStopReason;
     }
 
@@ -406,7 +532,8 @@
         } else {
             dest.writeInt(0);
         }
-        dest.writeInt(stopReason);
+        dest.writeInt(mStopReason);
+        dest.writeInt(mLegacyStopReason);
         dest.writeString(debugStopReason);
     }
 
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index 0f3d299..fa7a2d3 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -139,19 +139,23 @@
      * Once this method is called, you no longer need to call
      * {@link #jobFinished(JobParameters, boolean)}.
      *
-     * <p>This will happen if the requirements specified at schedule time are no longer met. For
+     * <p>This may happen if the requirements specified at schedule time are no longer met. For
      * example you may have requested WiFi with
      * {@link android.app.job.JobInfo.Builder#setRequiredNetworkType(int)}, yet while your
      * job was executing the user toggled WiFi. Another example is if you had specified
-     * {@link android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its
-     * idle maintenance window. You are solely responsible for the behavior of your application
-     * upon receipt of this message; your app will likely start to misbehave if you ignore it.
+     * {@link android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)}, and the phone left
+     * its idle maintenance window. There are many other reasons a job can be stopped early besides
+     * constraints no longer being satisfied. {@link JobParameters#getStopReason()} will return the
+     * reason this method was called. You are solely responsible for the behavior of your
+     * application upon receipt of this message; your app will likely start to misbehave if you
+     * ignore it.
      * <p>
      * Once this method returns (or times out), the system releases the wakelock that it is holding
      * on behalf of the job.</p>
      *
-     * @param params The parameters identifying this job, as supplied to
-     *               the job in the {@link #onStartJob(JobParameters)} callback.
+     * @param params The parameters identifying this job, similar to what was supplied to the job in
+     *               the {@link #onStartJob(JobParameters)} callback, but with the stop reason
+     *               included.
      * @return {@code true} to indicate to the JobManager whether you'd like to reschedule
      * this job based on the retry criteria provided at job creation-time; or {@code false}
      * to end the job entirely.  Regardless of the value returned, your job must stop executing.
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index 88f21a5..63b3959 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -40,9 +40,9 @@
 /**
  * Interface to access and modify the permanent and temporary power save allow list. The two lists
  * are kept separately. Apps placed on the permanent allow list are only removed via an explicit
- * {@link #removeFromAllowList(String)} call. Apps allow-listed by default by the system cannot be
- * removed. Apps placed on the temporary allow list are removed from that allow list after a
- * predetermined amount of time.
+ * {@link #removeFromPermanentAllowList(String)} call. Apps allow-listed by default by the system
+ * cannot be removed. Apps placed on the temporary allow list are removed from that allow list after
+ * a predetermined amount of time.
  *
  * @hide
  */
@@ -402,9 +402,9 @@
      *
      * @param includingIdle Set to true if the app should be allow-listed from device idle as well
      *                      as other power save restrictions
-     * @hide
      */
     @NonNull
+    @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
     public int[] getAllowListedAppIds(boolean includingIdle) {
         try {
             if (includingIdle) {
@@ -445,7 +445,7 @@
      * @param packageName The app to remove from the allow list
      */
     @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
-    public void removeFromAllowList(@NonNull String packageName) {
+    public void removeFromPermanentAllowList(@NonNull String packageName) {
         try {
             mService.removePowerSaveWhitelistApp(packageName);
         } catch (RemoteException e) {
@@ -463,8 +463,8 @@
      */
     @UserHandleAware
     @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
-    public void addToTemporaryAllowList(@NonNull String packageName, long durationMs,
-            @ReasonCode int reasonCode, @Nullable String reason) {
+    public void addToTemporaryAllowList(@NonNull String packageName, @ReasonCode int reasonCode,
+            @Nullable String reason, long durationMs) {
         try {
             mService.addPowerSaveTempWhitelistApp(packageName, durationMs, mContext.getUserId(),
                     reasonCode, reason);
@@ -488,7 +488,7 @@
     @UserHandleAware
     @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
     public long addToTemporaryAllowListForEvent(@NonNull String packageName,
-            @AllowListEvent int event, @ReasonCode int reasonCode, @Nullable String reason) {
+            @ReasonCode int reasonCode, @Nullable String reason, @AllowListEvent int event) {
         try {
             switch (event) {
                 case EVENT_MMS:
diff --git a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
index eba39c7..07231b0 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
@@ -439,12 +439,12 @@
      * whitelisted by default by the system cannot be removed.
      *
      * @param packageName The app to remove from the whitelist
-     * @deprecated Use {@link PowerExemptionManager#removeFromAllowList(String)} instead
+     * @deprecated Use {@link PowerExemptionManager#removeFromPermanentAllowList(String)} instead
      */
     @Deprecated
     @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
     public void removeFromWhitelist(@NonNull String packageName) {
-        mPowerExemptionManager.removeFromAllowList(packageName);
+        mPowerExemptionManager.removeFromPermanentAllowList(packageName);
     }
 
     /**
@@ -455,13 +455,13 @@
      * @param reasonCode one of {@link ReasonCode}, use {@link #REASON_UNKNOWN} if not sure.
      * @param reason a optional human readable reason string, could be null or empty string.
      * @deprecated Use {@link PowerExemptionManager#addToTemporaryAllowList(
-     *             String, long, int, String)} instead
+     *             String, int, String, long)} instead
      */
     @Deprecated
     @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
     public void whitelistAppTemporarily(@NonNull String packageName, long durationMs,
             @ReasonCode int reasonCode, @Nullable String reason) {
-        mPowerExemptionManager.addToTemporaryAllowList(packageName, durationMs, reasonCode, reason);
+        mPowerExemptionManager.addToTemporaryAllowList(packageName, reasonCode, reason, durationMs);
     }
 
     /**
@@ -470,13 +470,13 @@
      * @param packageName The package to add to the temp whitelist
      * @param durationMs  How long to keep the app on the temp whitelist for (in milliseconds)
      * @deprecated Use {@link PowerExemptionManager#addToTemporaryAllowList(
-     *             String, long, int, String)} instead
+     *             String, int, String, long)} instead
      */
     @Deprecated
     @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
     public void whitelistAppTemporarily(@NonNull String packageName, long durationMs) {
         mPowerExemptionManager.addToTemporaryAllowList(
-                packageName, durationMs, REASON_UNKNOWN, packageName);
+                packageName, REASON_UNKNOWN, packageName, durationMs);
     }
 
     /**
@@ -490,14 +490,14 @@
      *                    used for logging purposes. Could be null or empty string.
      * @return The duration (in milliseconds) that the app is whitelisted for
      * @deprecated Use {@link PowerExemptionManager#addToTemporaryAllowListForEvent(
-     *             String, int, int, String)} instead
+     *             String, int, String, int)} instead
      */
     @Deprecated
     @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
     public long whitelistAppTemporarilyForEvent(@NonNull String packageName,
             @WhitelistEvent int event, @Nullable String reason) {
         return mPowerExemptionManager.addToTemporaryAllowListForEvent(
-                packageName, event, REASON_UNKNOWN, reason);
+                packageName, REASON_UNKNOWN, reason, event);
     }
 
     /**
@@ -512,14 +512,14 @@
      *                    used for logging purposes. Could be null or empty string.
      * @return The duration (in milliseconds) that the app is whitelisted for
      * @deprecated Use {@link PowerExemptionManager#addToTemporaryAllowListForEvent(
-     *             String, int, int, String)} instead
+     *             String, int, String, int)} instead
      */
     @Deprecated
     @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
     public long whitelistAppTemporarilyForEvent(@NonNull String packageName,
             @WhitelistEvent int event, @ReasonCode int reasonCode, @Nullable String reason) {
         return mPowerExemptionManager.addToTemporaryAllowListForEvent(
-                packageName, event, reasonCode, reason);
+                packageName, reasonCode, reason, event);
     }
 
     /**
diff --git a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
index 7833a03..6ae91a0 100644
--- a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.app.job.JobInfo;
+import android.app.job.JobParameters;
 import android.util.proto.ProtoOutputStream;
 
 import java.util.List;
@@ -36,7 +37,7 @@
     /**
      * Cancel the jobs for a given uid (e.g. when app data is cleared)
      */
-    void cancelJobsForUid(int uid, String reason);
+    void cancelJobsForUid(int uid, @JobParameters.StopReason int reason, String debugReason);
 
     /**
      * These are for activity manager to communicate to use what is currently performing backups.
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index b958c3f..325be1b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -266,6 +266,8 @@
 
     String[] mRecycledPreemptReasonForContext = new String[MAX_JOB_CONTEXTS_COUNT];
 
+    int[] mRecycledPreemptReasonCodeForContext = new int[MAX_JOB_CONTEXTS_COUNT];
+
     String[] mRecycledShouldStopJobReason = new String[MAX_JOB_CONTEXTS_COUNT];
 
     private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>();
@@ -505,6 +507,7 @@
         int[] preferredUidForContext = mRecycledPreferredUidForContext;
         int[] workTypeForContext = mRecycledWorkTypeForContext;
         String[] preemptReasonForContext = mRecycledPreemptReasonForContext;
+        int[] preemptReasonCodeForContext = mRecycledPreemptReasonCodeForContext;
         String[] shouldStopJobReason = mRecycledShouldStopJobReason;
 
         updateCounterConfigLocked();
@@ -528,6 +531,7 @@
             slotChanged[i] = false;
             preferredUidForContext[i] = js.getPreferredUid();
             preemptReasonForContext[i] = null;
+            preemptReasonCodeForContext[i] = JobParameters.STOP_REASON_UNDEFINED;
             shouldStopJobReason[i] = shouldStopRunningJobLocked(js);
         }
         if (DEBUG) {
@@ -551,6 +555,7 @@
             int allWorkTypes = getJobWorkTypes(nextPending);
             int workType = mWorkCountTracker.canJobStart(allWorkTypes);
             boolean startingJob = false;
+            int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
             String preemptReason = null;
             // TODO(141645789): rewrite this to look at empty contexts first so we don't
             // unnecessarily preempt
@@ -582,6 +587,7 @@
                         // assign the new job to this context since we'll reassign when the
                         // preempted job finally stops.
                         preemptReason = reason;
+                        preemptReasonCode = JobParameters.STOP_REASON_DEVICE_STATE;
                     }
                     continue;
                 }
@@ -597,6 +603,7 @@
                     minPriorityForPreemption = jobPriority;
                     selectedContextId = j;
                     preemptReason = "higher priority job found";
+                    preemptReasonCode = JobParameters.STOP_REASON_PREEMPT;
                     // In this case, we're just going to preempt a low priority job, we're not
                     // actually starting a job, so don't set startingJob.
                 }
@@ -604,6 +611,7 @@
             if (selectedContextId != -1) {
                 contextIdToJobMap[selectedContextId] = nextPending;
                 slotChanged[selectedContextId] = true;
+                preemptReasonCodeForContext[selectedContextId] = preemptReasonCode;
                 preemptReasonForContext[selectedContextId] = preemptReason;
             }
             if (startingJob) {
@@ -631,8 +639,13 @@
                     }
                     // preferredUid will be set to uid of currently running job.
                     activeServices.get(i).cancelExecutingJobLocked(
+                            preemptReasonCodeForContext[i],
                             JobParameters.REASON_PREEMPT, preemptReasonForContext[i]);
-                    preservePreferredUid = true;
+                    // Only preserve the UID if we're preempting for the same UID. If we're stopping
+                    // the job because something is pending (eg. EJs), then we shouldn't preserve
+                    // the UID.
+                    preservePreferredUid =
+                            preemptReasonCodeForContext[i] == JobParameters.STOP_REASON_PREEMPT;
                 } else {
                     final JobStatus pendingJob = contextIdToJobMap[i];
                     if (DEBUG) {
@@ -657,7 +670,8 @@
             final JobStatus jobStatus = jsc.getRunningJobLocked();
 
             if (jobStatus != null && !jsc.isWithinExecutionGuaranteeTime()) {
-                jsc.cancelExecutingJobLocked(JobParameters.REASON_TIMEOUT, debugReason);
+                jsc.cancelExecutingJobLocked(JobParameters.STOP_REASON_DEVICE_STATE,
+                        JobParameters.REASON_TIMEOUT, debugReason);
             }
         }
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java b/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java
index 6ffac91..02f9129 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java
@@ -374,7 +374,7 @@
                             pw.print(pe.stopReasons.valueAt(k));
                             pw.print("x ");
                             pw.print(JobParameters
-                                    .getReasonCodeDescription(pe.stopReasons.keyAt(k)));
+                                    .getLegacyReasonCodeDescription(pe.stopReasons.keyAt(k)));
                         }
                         pw.println();
                     }
@@ -621,7 +621,7 @@
                 if (reason != null) {
                     pw.print(mEventReasons[index]);
                 } else {
-                    pw.print(JobParameters.getReasonCodeDescription(
+                    pw.print(JobParameters.getLegacyReasonCodeDescription(
                             (mEventCmds[index] & EVENT_STOP_REASON_MASK)
                                     >> EVENT_STOP_REASON_SHIFT));
                 }
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 2b08ba5..a041f8c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -746,8 +746,11 @@
                                             Slog.d(TAG, "Removing jobs for package " + pkgName
                                                     + " in user " + userId);
                                         }
+                                        // By the time we get here, the process should have already
+                                        // been stopped, so the app wouldn't get the stop reason,
+                                        // so just put USER instead of UNINSTALL or DISABLED.
                                         cancelJobsForPackageAndUid(pkgName, pkgUid,
-                                                "app disabled");
+                                                JobParameters.STOP_REASON_USER, "app disabled");
                                     }
                                 } catch (RemoteException|IllegalArgumentException e) {
                                     /*
@@ -785,7 +788,11 @@
                     if (DEBUG) {
                         Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
                     }
-                    cancelJobsForPackageAndUid(pkgName, uidRemoved, "app uninstalled");
+                    // By the time we get here, the process should have already
+                    // been stopped, so the app wouldn't get the stop reason,
+                    // so just put USER instead of UNINSTALL or DISABLED.
+                    cancelJobsForPackageAndUid(pkgName, uidRemoved,
+                            JobParameters.STOP_REASON_USER, "app uninstalled");
                     synchronized (mLock) {
                         for (int c = 0; c < mControllers.size(); ++c) {
                             mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid);
@@ -837,7 +844,8 @@
                     if (DEBUG) {
                         Slog.d(TAG, "Removing jobs for pkg " + pkgName + " at uid " + pkgUid);
                     }
-                    cancelJobsForPackageAndUid(pkgName, pkgUid, "app force stopped");
+                    cancelJobsForPackageAndUid(pkgName, pkgUid,
+                            JobParameters.STOP_REASON_USER, "app force stopped");
                 }
             }
         }
@@ -924,8 +932,7 @@
         final String servicePkg = job.getService().getPackageName();
         if (job.isPersisted() && (packageName == null || packageName.equals(servicePkg))) {
             // Only limit schedule calls for persisted jobs scheduled by the app itself.
-            final String pkg =
-                    packageName == null ? job.getService().getPackageName() : packageName;
+            final String pkg = packageName == null ? servicePkg : packageName;
             if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG)) {
                 if (mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_LOGGED)) {
                     // Don't log too frequently
@@ -972,14 +979,10 @@
             mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG);
         }
 
-        try {
-            if (ActivityManager.getService().isAppStartModeDisabled(uId,
-                    job.getService().getPackageName())) {
-                Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
-                        + " -- package not allowed to start");
-                return JobScheduler.RESULT_FAILURE;
-            }
-        } catch (RemoteException e) {
+        if (mActivityManagerInternal.isAppStartModeDisabled(uId, servicePkg)) {
+            Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
+                    + " -- package not allowed to start");
+            return JobScheduler.RESULT_FAILURE;
         }
 
         synchronized (mLock) {
@@ -1029,7 +1032,8 @@
 
             if (toCancel != null) {
                 // Implicitly replaces the existing job record with the new instance
-                cancelJobImplLocked(toCancel, jobStatus, "job rescheduled by app");
+                cancelJobImplLocked(toCancel, jobStatus, JobParameters.STOP_REASON_CANCELLED_BY_APP,
+                        "job rescheduled by app");
             } else {
                 startTrackingJobLocked(jobStatus, null);
             }
@@ -1105,7 +1109,10 @@
             final List<JobStatus> jobsForUser = mJobs.getJobsByUser(userHandle);
             for (int i=0; i<jobsForUser.size(); i++) {
                 JobStatus toRemove = jobsForUser.get(i);
-                cancelJobImplLocked(toRemove, null, "user removed");
+                // By the time we get here, the process should have already been stopped, so the
+                // app wouldn't get the stop reason, so just put USER instead of UNINSTALL.
+                cancelJobImplLocked(toRemove, null, JobParameters.STOP_REASON_USER,
+                        "user removed");
             }
         }
     }
@@ -1117,7 +1124,8 @@
         }
     }
 
-    void cancelJobsForPackageAndUid(String pkgName, int uid, String reason) {
+    void cancelJobsForPackageAndUid(String pkgName, int uid, @JobParameters.StopReason int reason,
+            String debugReason) {
         if ("android".equals(pkgName)) {
             Slog.wtfStack(TAG, "Can't cancel all jobs for system package");
             return;
@@ -1127,7 +1135,7 @@
             for (int i = jobsForUid.size() - 1; i >= 0; i--) {
                 final JobStatus job = jobsForUid.get(i);
                 if (job.getSourcePackageName().equals(pkgName)) {
-                    cancelJobImplLocked(job, null, reason);
+                    cancelJobImplLocked(job, null, reason, debugReason);
                 }
             }
         }
@@ -1137,10 +1145,11 @@
      * Entry point from client to cancel all jobs originating from their uid.
      * This will remove the job from the master list, and cancel the job if it was staged for
      * execution or being executed.
-     * @param uid Uid to check against for removal of a job.
      *
+     * @param uid Uid to check against for removal of a job.
      */
-    public boolean cancelJobsForUid(int uid, String reason) {
+    public boolean cancelJobsForUid(int uid, @JobParameters.StopReason int reason,
+            String debugReason) {
         if (uid == Process.SYSTEM_UID) {
             Slog.wtfStack(TAG, "Can't cancel all jobs for system uid");
             return false;
@@ -1151,7 +1160,7 @@
             final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
             for (int i=0; i<jobsForUid.size(); i++) {
                 JobStatus toRemove = jobsForUid.get(i);
-                cancelJobImplLocked(toRemove, null, reason);
+                cancelJobImplLocked(toRemove, null, reason, debugReason);
                 jobsCanceled = true;
             }
         }
@@ -1162,15 +1171,17 @@
      * Entry point from client to cancel the job corresponding to the jobId provided.
      * This will remove the job from the master list, and cancel the job if it was staged for
      * execution or being executed.
-     * @param uid Uid of the calling client.
+     *
+     * @param uid   Uid of the calling client.
      * @param jobId Id of the job, provided at schedule-time.
      */
-    public boolean cancelJob(int uid, int jobId, int callingUid) {
+    private boolean cancelJob(int uid, int jobId, int callingUid,
+            @JobParameters.StopReason int reason) {
         JobStatus toCancel;
         synchronized (mLock) {
             toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
             if (toCancel != null) {
-                cancelJobImplLocked(toCancel, null,
+                cancelJobImplLocked(toCancel, null, reason,
                         "cancel() called by app, callingUid=" + callingUid
                         + " uid=" + uid + " jobId=" + jobId);
             }
@@ -1184,7 +1195,8 @@
      * {@code incomingJob} is non-null, it replaces {@code cancelled} in the store of
      * currently scheduled jobs.
      */
-    private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob, String reason) {
+    private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob,
+            @JobParameters.StopReason int reason, String debugReason) {
         if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
         cancelled.unprepareLocked();
         stopTrackingJobLocked(cancelled, incomingJob, true /* writeBack */);
@@ -1193,7 +1205,8 @@
             mJobPackageTracker.noteNonpending(cancelled);
         }
         // Cancel if running.
-        stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED, reason);
+        stopJobOnServiceContextLocked(cancelled, reason, JobParameters.REASON_CANCELED,
+                debugReason);
         // If this is a replacement, bring in the new version of the job
         if (incomingJob != null) {
             if (DEBUG) Slog.i(TAG, "Tracking replacement job " + incomingJob.toShortString());
@@ -1232,7 +1245,8 @@
                     JobServiceContext jsc = mActiveServices.get(i);
                     final JobStatus executing = jsc.getRunningJobLocked();
                     if (executing != null && !executing.canRunInDoze()) {
-                        jsc.cancelExecutingJobLocked(JobParameters.REASON_DEVICE_IDLE,
+                        jsc.cancelExecutingJobLocked(JobParameters.STOP_REASON_DEVICE_STATE,
+                                JobParameters.REASON_DEVICE_IDLE,
                                 "cancelled due to doze");
                     }
                 }
@@ -1430,7 +1444,8 @@
                 if (DEBUG) {
                     Slog.v(TAG, "  replacing " + oldJob + " with " + newJob);
                 }
-                cancelJobImplLocked(oldJob, newJob, "deferred rtc calculation");
+                cancelJobImplLocked(oldJob, newJob, JobParameters.STOP_REASON_SYSTEM_PROCESSING,
+                        "deferred rtc calculation");
             }
         }
     };
@@ -1550,12 +1565,13 @@
         return removed;
     }
 
-    private boolean stopJobOnServiceContextLocked(JobStatus job, int reason, String debugReason) {
+    private boolean stopJobOnServiceContextLocked(JobStatus job,
+            @JobParameters.StopReason int reason, int legacyReason, String debugReason) {
         for (int i=0; i<mActiveServices.size(); i++) {
             JobServiceContext jsc = mActiveServices.get(i);
             final JobStatus executing = jsc.getRunningJobLocked();
             if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
-                jsc.cancelExecutingJobLocked(reason, debugReason);
+                jsc.cancelExecutingJobLocked(reason, legacyReason, debugReason);
                 return true;
             }
         }
@@ -1880,7 +1896,7 @@
                         queueReadyJobsForExecutionLocked();
                         break;
                     case MSG_STOP_JOB:
-                        cancelJobImplLocked((JobStatus) message.obj, null,
+                        cancelJobImplLocked((JobStatus) message.obj, null, message.arg1,
                                 "app no longer allowed to run");
                         break;
 
@@ -1895,7 +1911,9 @@
                         final boolean disabled = message.arg2 != 0;
                         updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
                         if (disabled) {
-                            cancelJobsForUid(uid, "uid gone");
+                            cancelJobsForUid(uid,
+                                    JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
+                                    "uid gone");
                         }
                         synchronized (mLock) {
                             mDeviceIdleJobsController.setUidActiveLocked(uid, false);
@@ -1913,7 +1931,9 @@
                         final int uid = message.arg1;
                         final boolean disabled = message.arg2 != 0;
                         if (disabled) {
-                            cancelJobsForUid(uid, "app uid idle");
+                            cancelJobsForUid(uid,
+                                    JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
+                                    "app uid idle");
                         }
                         synchronized (mLock) {
                             mDeviceIdleJobsController.setUidActiveLocked(uid, false);
@@ -1965,10 +1985,12 @@
                 if (running.getEffectiveStandbyBucket() == RESTRICTED_INDEX
                         && !running.areDynamicConstraintsSatisfied()) {
                     serviceContext.cancelExecutingJobLocked(
+                            running.getStopReason(),
                             JobParameters.REASON_RESTRICTED_BUCKET,
                             "cancelled due to restricted bucket");
                 } else {
                     serviceContext.cancelExecutingJobLocked(
+                            running.getStopReason(),
                             JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED,
                             "cancelled due to unsatisfied constraints");
                 }
@@ -1977,7 +1999,9 @@
                 if (restriction != null) {
                     final int reason = restriction.getReason();
                     serviceContext.cancelExecutingJobLocked(reason,
-                            "restricted due to " + JobParameters.getReasonCodeDescription(reason));
+                            restriction.getLegacyReason(),
+                            "restricted due to " + JobParameters.getLegacyReasonCodeDescription(
+                                    reason));
                 }
             }
         }
@@ -2058,15 +2082,14 @@
         @Override
         public void accept(JobStatus job) {
             if (isReadyToBeExecutedLocked(job)) {
-                try {
-                    if (ActivityManager.getService().isAppStartModeDisabled(job.getUid(),
-                            job.getJob().getService().getPackageName())) {
-                        Slog.w(TAG, "Aborting job " + job.getUid() + ":"
-                                + job.getJob().toString() + " -- package not allowed to start");
-                        mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
-                        return;
-                    }
-                } catch (RemoteException e) {
+                if (mActivityManagerInternal.isAppStartModeDisabled(job.getUid(),
+                        job.getJob().getService().getPackageName())) {
+                    Slog.w(TAG, "Aborting job " + job.getUid() + ":"
+                            + job.getJob().toString() + " -- package not allowed to start");
+                    mHandler.obtainMessage(MSG_STOP_JOB,
+                            JobParameters.STOP_REASON_BACKGROUND_RESTRICTION, 0, job)
+                            .sendToTarget();
+                    return;
                 }
 
                 final boolean shouldForceBatchJob;
@@ -2276,7 +2299,7 @@
         if (restriction != null) {
             if (DEBUG) {
                 Slog.v(TAG, "areComponentsInPlaceLocked: " + job.toShortString()
-                        + " restricted due to " + restriction.getReason());
+                        + " restricted due to " + restriction.getLegacyReason());
             }
             return false;
         }
@@ -2367,8 +2390,9 @@
         }
 
         @Override
-        public void cancelJobsForUid(int uid, String reason) {
-            JobSchedulerService.this.cancelJobsForUid(uid, reason);
+        public void cancelJobsForUid(int uid, @JobParameters.StopReason int reason,
+                String debugReason) {
+            JobSchedulerService.this.cancelJobsForUid(uid, reason, debugReason);
         }
 
         @Override
@@ -2706,6 +2730,7 @@
             final long ident = Binder.clearCallingIdentity();
             try {
                 JobSchedulerService.this.cancelJobsForUid(uid,
+                        JobParameters.STOP_REASON_CANCELLED_BY_APP,
                         "cancelAll() called by app, callingUid=" + uid);
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -2718,7 +2743,8 @@
 
             final long ident = Binder.clearCallingIdentity();
             try {
-                JobSchedulerService.this.cancelJob(uid, jobId, uid);
+                JobSchedulerService.this.cancelJob(uid, jobId, uid,
+                        JobParameters.STOP_REASON_CANCELLED_BY_APP);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -2924,12 +2950,13 @@
 
         if (!hasJobId) {
             pw.println("Canceling all jobs for " + pkgName + " in user " + userId);
-            if (!cancelJobsForUid(pkgUid, "cancel shell command for package")) {
+            if (!cancelJobsForUid(pkgUid, JobParameters.STOP_REASON_USER,
+                    "cancel shell command for package")) {
                 pw.println("No matching jobs found.");
             }
         } else {
             pw.println("Canceling job " + pkgName + "/#" + jobId + " in user " + userId);
-            if (!cancelJob(pkgUid, jobId, Process.SHELL_UID)) {
+            if (!cancelJob(pkgUid, jobId, Process.SHELL_UID, JobParameters.STOP_REASON_USER)) {
                 pw.println("No matching job found.");
             }
         }
@@ -3164,8 +3191,9 @@
                         for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
                             final JobRestriction restriction = mJobRestrictions.get(i);
                             if (restriction.isJobRestricted(job)) {
-                                final int reason = restriction.getReason();
-                                pw.print(" " + JobParameters.getReasonCodeDescription(reason));
+                                final int reason = restriction.getLegacyReason();
+                                pw.print(" ");
+                                pw.print(JobParameters.getLegacyReasonCodeDescription(reason));
                             }
                         }
                     } else {
@@ -3430,7 +3458,7 @@
                         final long restrictionsToken = proto.start(
                                 JobSchedulerServiceDumpProto.RegisteredJob.RESTRICTIONS);
                         proto.write(JobSchedulerServiceDumpProto.JobRestriction.REASON,
-                                restriction.getReason());
+                                restriction.getLegacyReason());
                         proto.write(JobSchedulerServiceDumpProto.JobRestriction.IS_RESTRICTING,
                                 restriction.isJobRestricted(job));
                         proto.end(restrictionsToken);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 9ef46df..e8bcbfb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -354,8 +354,9 @@
 
     /** Called externally when a job that was scheduled for execution should be cancelled. */
     @GuardedBy("mLock")
-    void cancelExecutingJobLocked(int reason, @NonNull String debugReason) {
-        doCancelLocked(reason, debugReason);
+    void cancelExecutingJobLocked(@JobParameters.StopReason int reason,
+            int legacyStopReason, @NonNull String debugReason) {
+        doCancelLocked(reason, legacyStopReason, debugReason);
     }
 
     int getPreferredUid() {
@@ -387,7 +388,8 @@
                 && (pkgName == null || pkgName.equals(executing.getSourcePackageName()))
                 && (!matchJobId || jobId == executing.getJobId())) {
             if (mVerb == VERB_EXECUTING) {
-                mParams.setStopReason(JobParameters.REASON_TIMEOUT, reason);
+                mParams.setStopReason(JobParameters.STOP_REASON_TIMEOUT,
+                        JobParameters.REASON_TIMEOUT, reason);
                 sendStopMessageLocked("force timeout from shell");
                 return true;
             }
@@ -614,7 +616,8 @@
     }
 
     @GuardedBy("mLock")
-    private void doCancelLocked(int stopReasonCode, @Nullable String debugReason) {
+    private void doCancelLocked(@JobParameters.StopReason int stopReasonCode, int legacyStopReason,
+            @Nullable String debugReason) {
         if (mVerb == VERB_FINISHED) {
             if (DEBUG) {
                 Slog.d(TAG,
@@ -622,8 +625,8 @@
             }
             return;
         }
-        mParams.setStopReason(stopReasonCode, debugReason);
-        if (stopReasonCode == JobParameters.REASON_PREEMPT) {
+        mParams.setStopReason(stopReasonCode, legacyStopReason, debugReason);
+        if (legacyStopReason == JobParameters.REASON_PREEMPT) {
             mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
                     NO_PREFERRED_UID;
         }
@@ -781,7 +784,8 @@
                     // Not an error - client ran out of time.
                     Slog.i(TAG, "Client timed out while executing (no jobFinished received)."
                             + " Sending onStop: " + getRunningJobNameLocked());
-                    mParams.setStopReason(JobParameters.REASON_TIMEOUT, "client timed out");
+                    mParams.setStopReason(JobParameters.STOP_REASON_TIMEOUT,
+                            JobParameters.REASON_TIMEOUT, "client timed out");
                     sendStopMessageLocked("timeout while executing");
                 } else {
                     // We've given the app the minimum execution time. See if we should stop it or
@@ -790,7 +794,11 @@
                     if (reason != null) {
                         Slog.i(TAG, "Stopping client after min execution time: "
                                 + getRunningJobNameLocked() + " because " + reason);
-                        mParams.setStopReason(JobParameters.REASON_TIMEOUT, reason);
+                        // Tell the developer we're stopping the job due to device state instead
+                        // of timeout since all of the reasons could equate to "the system needs
+                        // the resources the app is currently using."
+                        mParams.setStopReason(JobParameters.STOP_REASON_DEVICE_STATE,
+                                JobParameters.REASON_TIMEOUT, reason);
                         sendStopMessageLocked(reason);
                     } else {
                         Slog.i(TAG, "Letting " + getRunningJobNameLocked()
@@ -844,12 +852,12 @@
         }
         applyStoppedReasonLocked(reason);
         completedJob = mRunningJob;
-        final int stopReason = mParams.getStopReason();
-        mJobPackageTracker.noteInactive(completedJob, stopReason, reason);
+        final int legacyStopReason = mParams.getLegacyStopReason();
+        mJobPackageTracker.noteInactive(completedJob, legacyStopReason, reason);
         FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
                 completedJob.getSourceUid(), null, completedJob.getBatteryName(),
                 FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__FINISHED,
-                stopReason, completedJob.getStandbyBucket(), completedJob.getJobId(),
+                legacyStopReason, completedJob.getStandbyBucket(), completedJob.getJobId(),
                 completedJob.hasChargingConstraint(),
                 completedJob.hasBatteryNotLowConstraint(),
                 completedJob.hasStorageNotLowConstraint(),
@@ -860,7 +868,7 @@
                 completedJob.hasContentTriggerConstraint());
         try {
             mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(),
-                    stopReason);
+                    legacyStopReason);
         } catch (RemoteException e) {
             // Whatever.
         }
@@ -879,7 +887,7 @@
         service = null;
         mAvailable = true;
         removeOpTimeOutLocked();
-        mCompletedListener.onJobCompletedLocked(completedJob, stopReason, reschedule);
+        mCompletedListener.onJobCompletedLocked(completedJob, legacyStopReason, reschedule);
         mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType);
     }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index aa8d98c..9cd3a8f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -541,18 +541,23 @@
         /**
          * Write out a tag with data identifying this job's constraints. If the constraint isn't here
          * it doesn't apply.
+         * TODO: b/183455312 Update this code to use proper serialization for NetworkRequest,
+         *       because currently store is not including everything (like, UIDs, bandwidth,
+         *       signal strength etc. are lost).
          */
         private void writeConstraintsToXml(XmlSerializer out, JobStatus jobStatus) throws IOException {
             out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS);
             if (jobStatus.hasConnectivityConstraint()) {
                 final NetworkRequest network = jobStatus.getJob().getRequiredNetwork();
+                // STOPSHIP b/183071974: improve the scheme for backward compatibility and
+                // mainline cleanliness.
                 out.attribute(null, "net-capabilities", Long.toString(
-                        BitUtils.packBits(network.networkCapabilities.getCapabilities())));
+                        BitUtils.packBits(network.getCapabilities())));
                 out.attribute(null, "net-unwanted-capabilities", Long.toString(
-                        BitUtils.packBits(network.networkCapabilities.getUnwantedCapabilities())));
+                        BitUtils.packBits(network.getUnwantedCapabilities())));
 
                 out.attribute(null, "net-transport-types", Long.toString(
-                        BitUtils.packBits(network.networkCapabilities.getTransportTypes())));
+                        BitUtils.packBits(network.getTransportTypes())));
             }
             if (jobStatus.hasIdleConstraint()) {
                 out.attribute(null, "idle", Boolean.toString(true));
@@ -976,18 +981,23 @@
                     null, "net-unwanted-capabilities");
             final String netTransportTypes = parser.getAttributeValue(null, "net-transport-types");
             if (netCapabilities != null && netTransportTypes != null) {
-                final NetworkRequest request = new NetworkRequest.Builder().build();
+                final NetworkRequest.Builder builder = new NetworkRequest.Builder()
+                        .clearCapabilities();
                 final long unwantedCapabilities = netUnwantedCapabilities != null
                         ? Long.parseLong(netUnwantedCapabilities)
-                        : BitUtils.packBits(request.networkCapabilities.getUnwantedCapabilities());
-
+                        : BitUtils.packBits(builder.build().getUnwantedCapabilities());
                 // We're okay throwing NFE here; caught by caller
-                request.networkCapabilities.setCapabilities(
-                        BitUtils.unpackBits(Long.parseLong(netCapabilities)),
-                        BitUtils.unpackBits(unwantedCapabilities));
-                request.networkCapabilities.setTransportTypes(
-                        BitUtils.unpackBits(Long.parseLong(netTransportTypes)));
-                jobBuilder.setRequiredNetwork(request);
+                for (int capability : BitUtils.unpackBits(Long.parseLong(netCapabilities))) {
+                    builder.addCapability(capability);
+                }
+                for (int unwantedCapability : BitUtils.unpackBits(
+                        Long.parseLong(netUnwantedCapabilities))) {
+                    builder.addUnwantedCapability(unwantedCapability);
+                }
+                for (int transport : BitUtils.unpackBits(Long.parseLong(netTransportTypes))) {
+                    builder.addTransportType(transport);
+                }
+                jobBuilder.setRequiredNetwork(builder.build());
             } else {
                 // Read legacy values
                 val = parser.getAttributeValue(null, "connectivity");
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index a230b23..548a1ac 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -210,7 +210,8 @@
             jobStatus.maybeLogBucketMismatch();
         }
         boolean didChange =
-                jobStatus.setBackgroundNotRestrictedConstraintSatisfied(nowElapsed, canRun);
+                jobStatus.setBackgroundNotRestrictedConstraintSatisfied(nowElapsed, canRun,
+                        !mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, packageName));
         didChange |= jobStatus.setUidActive(isActive);
         return didChange;
     }
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 6e542f3..6825cf2 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
@@ -22,6 +22,7 @@
 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.job.JobInfo;
 import android.net.ConnectivityManager;
@@ -380,15 +381,23 @@
         }
     }
 
+    private static NetworkCapabilities.Builder copyCapabilities(
+            @NonNull final NetworkRequest request) {
+        final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
+        for (int transport : request.getTransportTypes()) builder.addTransportType(transport);
+        for (int capability : request.getCapabilities()) builder.addCapability(capability);
+        return builder;
+    }
+
     private static boolean isStrictSatisfied(JobStatus jobStatus, Network network,
             NetworkCapabilities capabilities, Constants constants) {
         // A restricted job that's out of quota MUST use an unmetered network.
         if (jobStatus.getEffectiveStandbyBucket() == RESTRICTED_INDEX
                 && !jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) {
-            final NetworkCapabilities required = new NetworkCapabilities.Builder(
-                    jobStatus.getJob().getRequiredNetwork().networkCapabilities)
-                    .addCapability(NET_CAPABILITY_NOT_METERED).build();
-            return required.satisfiedByNetworkCapabilities(capabilities);
+            final NetworkCapabilities.Builder builder =
+                    copyCapabilities(jobStatus.getJob().getRequiredNetwork());
+            builder.addCapability(NET_CAPABILITY_NOT_METERED);
+            return builder.build().satisfiedByNetworkCapabilities(capabilities);
         } else {
             return jobStatus.getJob().getRequiredNetwork().canBeSatisfiedBy(capabilities);
         }
@@ -402,10 +411,10 @@
         }
 
         // See if we match after relaxing any unmetered request
-        final NetworkCapabilities relaxed = new NetworkCapabilities.Builder(
-                jobStatus.getJob().getRequiredNetwork().networkCapabilities)
-                        .removeCapability(NET_CAPABILITY_NOT_METERED).build();
-        if (relaxed.satisfiedByNetworkCapabilities(capabilities)) {
+        final NetworkCapabilities.Builder builder =
+                copyCapabilities(jobStatus.getJob().getRequiredNetwork());
+        builder.removeCapability(NET_CAPABILITY_NOT_METERED);
+        if (builder.build().satisfiedByNetworkCapabilities(capabilities)) {
             // TODO: treat this as "maybe" response; need to check quotas
             return jobStatus.getFractionRunTime() > constants.CONN_PREFETCH_RELAX_FRAC;
         } else {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index bad8dc1..8fa0d3e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -24,11 +24,13 @@
 
 import android.app.AppGlobals;
 import android.app.job.JobInfo;
+import android.app.job.JobParameters;
 import android.app.job.JobWorkItem;
 import android.content.ClipData;
 import android.content.ComponentName;
 import android.content.pm.ServiceInfo;
 import android.net.Network;
+import android.net.NetworkRequest;
 import android.net.Uri;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -37,6 +39,7 @@
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.Pair;
+import android.util.Range;
 import android.util.Slog;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
@@ -54,6 +57,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.function.Predicate;
 
 /**
@@ -353,6 +357,9 @@
      */
     private long mLastFailedRunTime;
 
+    /** Whether or not the app is background restricted by the user (FAS). */
+    private boolean mIsUserBgRestricted;
+
     /**
      * Transient: when a job is inflated from disk before we have a reliable RTC clock time,
      * we retain the canonical (delay, deadline) scheduling tuple read out of the persistent
@@ -409,6 +416,9 @@
     /** The job's dynamic requirements have been satisfied. */
     private boolean mReadyDynamicSatisfied;
 
+    /** The reason a job most recently went from ready to not ready. */
+    private int mReasonReadyToUnready = JobParameters.STOP_REASON_UNDEFINED;
+
     /** Provide a handle to the service that this job will be run on. */
     public int getServiceToken() {
         return callingUid;
@@ -520,8 +530,15 @@
             // Later, when we check if a given network satisfies the required
             // network, we need to know the UID that is requesting it, so push
             // our source UID into place.
-            job.getRequiredNetwork().networkCapabilities.setSingleUid(this.sourceUid);
+            final JobInfo.Builder builder = new JobInfo.Builder(job);
+            final NetworkRequest.Builder requestBuilder =
+                    new NetworkRequest.Builder(job.getRequiredNetwork());
+            requestBuilder.setUids(
+                    Collections.singleton(new Range<Integer>(this.sourceUid, this.sourceUid)));
+            builder.setRequiredNetwork(requestBuilder.build());
+            job = builder.build();
         }
+
         final JobSchedulerInternal jsi = LocalServices.getService(JobSchedulerInternal.class);
         mHasMediaBackupExemption = !job.hasLateConstraint() && exemptedMediaUrisOnly
                 && requiresNetwork && this.sourcePackageName.equals(jsi.getMediaBackupPackage());
@@ -1042,6 +1059,11 @@
         mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsed;
     }
 
+    @JobParameters.StopReason
+    public int getStopReason() {
+        return mReasonReadyToUnready;
+    }
+
     /**
      * Return the fractional position of "now" within the "run time" window of
      * this job.
@@ -1172,7 +1194,9 @@
     }
 
     /** @return true if the constraint was changed, false otherwise. */
-    boolean setBackgroundNotRestrictedConstraintSatisfied(final long nowElapsed, boolean state) {
+    boolean setBackgroundNotRestrictedConstraintSatisfied(final long nowElapsed, boolean state,
+            boolean isUserBgRestricted) {
+        mIsUserBgRestricted = isUserBgRestricted;
         if (setConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED, nowElapsed, state)) {
             // The constraint was changed. Update the ready flag.
             mReadyNotRestrictedInBg = state;
@@ -1226,6 +1250,7 @@
                     "Constraint " + constraint + " is " + (!state ? "NOT " : "") + "satisfied for "
                             + toShortString());
         }
+        final boolean wasReady = !state && isReady();
         satisfiedConstraints = (satisfiedConstraints&~constraint) | (state ? constraint : 0);
         mSatisfiedConstraintsOfInterest = satisfiedConstraints & CONSTRAINTS_OF_INTEREST;
         mReadyDynamicSatisfied = mDynamicConstraints != 0
@@ -1244,9 +1269,81 @@
         mConstraintChangeHistoryIndex =
                 (mConstraintChangeHistoryIndex + 1) % NUM_CONSTRAINT_CHANGE_HISTORY;
 
+        // Can't use isReady() directly since "cache booleans" haven't updated yet.
+        final boolean isReady = readinessStatusWithConstraint(constraint, state);
+        if (wasReady && !isReady) {
+            mReasonReadyToUnready = constraintToStopReason(constraint);
+        } else if (!wasReady && isReady) {
+            mReasonReadyToUnready = JobParameters.STOP_REASON_UNDEFINED;
+        }
+
         return true;
     }
 
+    @JobParameters.StopReason
+    private int constraintToStopReason(int constraint) {
+        switch (constraint) {
+            case CONSTRAINT_BATTERY_NOT_LOW:
+                if ((requiredConstraints & constraint) != 0) {
+                    // The developer requested this constraint, so it makes sense to return the
+                    // explicit constraint reason.
+                    return JobParameters.STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW;
+                }
+                // Hard-coding right now since the current dynamic constraint sets don't overlap
+                // TODO: return based on active dynamic constraint sets when they start overlapping
+                return JobParameters.STOP_REASON_APP_STANDBY;
+            case CONSTRAINT_CHARGING:
+                if ((requiredConstraints & constraint) != 0) {
+                    // The developer requested this constraint, so it makes sense to return the
+                    // explicit constraint reason.
+                    return JobParameters.STOP_REASON_CONSTRAINT_CHARGING;
+                }
+                // Hard-coding right now since the current dynamic constraint sets don't overlap
+                // TODO: return based on active dynamic constraint sets when they start overlapping
+                return JobParameters.STOP_REASON_APP_STANDBY;
+            case CONSTRAINT_CONNECTIVITY:
+                return JobParameters.STOP_REASON_CONSTRAINT_CONNECTIVITY;
+            case CONSTRAINT_IDLE:
+                if ((requiredConstraints & constraint) != 0) {
+                    // The developer requested this constraint, so it makes sense to return the
+                    // explicit constraint reason.
+                    return JobParameters.STOP_REASON_CONSTRAINT_DEVICE_IDLE;
+                }
+                // Hard-coding right now since the current dynamic constraint sets don't overlap
+                // TODO: return based on active dynamic constraint sets when they start overlapping
+                return JobParameters.STOP_REASON_APP_STANDBY;
+            case CONSTRAINT_STORAGE_NOT_LOW:
+                return JobParameters.STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW;
+
+            case CONSTRAINT_BACKGROUND_NOT_RESTRICTED:
+                // The BACKGROUND_NOT_RESTRICTED constraint could be dissatisfied either because
+                // the app is background restricted, or because we're restricting background work
+                // in battery saver. Assume that background restriction is the reason apps that
+                // are background restricted have their jobs stopped, and battery saver otherwise.
+                // This has the benefit of being consistent for background restricted apps
+                // (they'll always get BACKGROUND_RESTRICTION) as the reason, regardless of
+                // battery saver state.
+                if (mIsUserBgRestricted) {
+                    return JobParameters.STOP_REASON_BACKGROUND_RESTRICTION;
+                }
+                return JobParameters.STOP_REASON_DEVICE_STATE;
+            case CONSTRAINT_DEVICE_NOT_DOZING:
+                return JobParameters.STOP_REASON_DEVICE_STATE;
+
+            case CONSTRAINT_WITHIN_QUOTA:
+            case CONSTRAINT_WITHIN_EXPEDITED_QUOTA:
+                return JobParameters.STOP_REASON_QUOTA;
+
+            // These should never be stop reasons since they can never go from true to false.
+            case CONSTRAINT_CONTENT_TRIGGER:
+            case CONSTRAINT_DEADLINE:
+            case CONSTRAINT_TIMING_DELAY:
+            default:
+                Slog.wtf(TAG, "Unsupported constraint (" + constraint + ") --stop reason mapping");
+                return JobParameters.STOP_REASON_UNDEFINED;
+        }
+    }
+
     boolean isConstraintSatisfied(int constraint) {
         return (satisfiedConstraints&constraint) != 0;
     }
@@ -1330,33 +1427,42 @@
      * granted, based on its requirements.
      */
     boolean wouldBeReadyWithConstraint(int constraint) {
+        return readinessStatusWithConstraint(constraint, true);
+    }
+
+    private boolean readinessStatusWithConstraint(int constraint, boolean value) {
         boolean oldValue = false;
         int satisfied = mSatisfiedConstraintsOfInterest;
         switch (constraint) {
             case CONSTRAINT_BACKGROUND_NOT_RESTRICTED:
                 oldValue = mReadyNotRestrictedInBg;
-                mReadyNotRestrictedInBg = true;
+                mReadyNotRestrictedInBg = value;
                 break;
             case CONSTRAINT_DEADLINE:
                 oldValue = mReadyDeadlineSatisfied;
-                mReadyDeadlineSatisfied = true;
+                mReadyDeadlineSatisfied = value;
                 break;
             case CONSTRAINT_DEVICE_NOT_DOZING:
                 oldValue = mReadyNotDozing;
-                mReadyNotDozing = true;
+                mReadyNotDozing = value;
                 break;
             case CONSTRAINT_WITHIN_QUOTA:
                 oldValue = mReadyWithinQuota;
-                mReadyWithinQuota = true;
+                mReadyWithinQuota = value;
                 break;
             case CONSTRAINT_WITHIN_EXPEDITED_QUOTA:
                 oldValue = mReadyWithinExpeditedQuota;
-                mReadyWithinExpeditedQuota = true;
+                mReadyWithinExpeditedQuota = value;
                 break;
             default:
-                satisfied |= constraint;
+                if (value) {
+                    satisfied |= constraint;
+                } else {
+                    satisfied &= ~constraint;
+                }
                 mReadyDynamicSatisfied = mDynamicConstraints != 0
                         && mDynamicConstraints == (satisfied & mDynamicConstraints);
+
                 break;
         }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
index ac59f95..2962b10 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
@@ -17,6 +17,7 @@
 package com.android.server.job.restrictions;
 
 import android.app.job.JobInfo;
+import android.app.job.JobParameters;
 import android.util.IndentingPrintWriter;
 import android.util.proto.ProtoOutputStream;
 
@@ -26,9 +27,8 @@
 /**
  * Used by {@link JobSchedulerService} to impose additional restrictions regarding whether jobs
  * should be scheduled or not based on the state of the system/device.
- * Every restriction is associated with exactly one reason (from {@link
- * android.app.job.JobParameters#JOB_STOP_REASON_CODES}), which could be retrieved using {@link
- * #getReason()}.
+ * Every restriction is associated with exactly one stop reason, which could be retrieved using
+ * {@link #getReason()} (and the legacy reason via {@link #getLegacyReason()}).
  * Note, that this is not taken into account for the jobs that have priority
  * {@link JobInfo#PRIORITY_FOREGROUND_APP} or higher.
  */
@@ -36,10 +36,13 @@
 
     final JobSchedulerService mService;
     private final int mReason;
+    private final int mLegacyReason;
 
-    JobRestriction(JobSchedulerService service, int reason) {
+    JobRestriction(JobSchedulerService service, @JobParameters.StopReason int reason,
+            int legacyReason) {
         mService = service;
         mReason = reason;
+        mLegacyReason = legacyReason;
     }
 
     /**
@@ -66,7 +69,12 @@
     public abstract void dumpConstants(ProtoOutputStream proto);
 
     /** @return reason code for the Restriction. */
+    @JobParameters.StopReason
     public final int getReason() {
         return mReason;
     }
+
+    public final int getLegacyReason() {
+        return mLegacyReason;
+    }
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
index 954a5b8..8b699e9 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
@@ -34,7 +34,7 @@
     private PowerManager mPowerManager;
 
     public ThermalStatusRestriction(JobSchedulerService service) {
-        super(service, JobParameters.REASON_DEVICE_THERMAL);
+        super(service, JobParameters.STOP_REASON_DEVICE_STATE, JobParameters.REASON_DEVICE_THERMAL);
     }
 
     @Override
diff --git a/core/api/current.txt b/core/api/current.txt
index 7e2edd6..0f00b54 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1729,42 +1729,66 @@
     field @Deprecated public static final int secondary_text_dark_nodisable = 17170438; // 0x1060006
     field @Deprecated public static final int secondary_text_light = 17170439; // 0x1060007
     field @Deprecated public static final int secondary_text_light_nodisable = 17170440; // 0x1060008
-    field public static final int system_neutral_0 = 17170485; // 0x1060035
-    field public static final int system_neutral_100 = 17170487; // 0x1060037
-    field public static final int system_neutral_1000 = 17170496; // 0x1060040
-    field public static final int system_neutral_200 = 17170488; // 0x1060038
-    field public static final int system_neutral_300 = 17170489; // 0x1060039
-    field public static final int system_neutral_400 = 17170490; // 0x106003a
-    field public static final int system_neutral_50 = 17170486; // 0x1060036
-    field public static final int system_neutral_500 = 17170491; // 0x106003b
-    field public static final int system_neutral_600 = 17170492; // 0x106003c
-    field public static final int system_neutral_700 = 17170493; // 0x106003d
-    field public static final int system_neutral_800 = 17170494; // 0x106003e
-    field public static final int system_neutral_900 = 17170495; // 0x106003f
-    field public static final int system_primary_0 = 17170461; // 0x106001d
-    field public static final int system_primary_100 = 17170463; // 0x106001f
-    field public static final int system_primary_1000 = 17170472; // 0x1060028
-    field public static final int system_primary_200 = 17170464; // 0x1060020
-    field public static final int system_primary_300 = 17170465; // 0x1060021
-    field public static final int system_primary_400 = 17170466; // 0x1060022
-    field public static final int system_primary_50 = 17170462; // 0x106001e
-    field public static final int system_primary_500 = 17170467; // 0x1060023
-    field public static final int system_primary_600 = 17170468; // 0x1060024
-    field public static final int system_primary_700 = 17170469; // 0x1060025
-    field public static final int system_primary_800 = 17170470; // 0x1060026
-    field public static final int system_primary_900 = 17170471; // 0x1060027
-    field public static final int system_secondary_0 = 17170473; // 0x1060029
-    field public static final int system_secondary_100 = 17170475; // 0x106002b
-    field public static final int system_secondary_1000 = 17170484; // 0x1060034
-    field public static final int system_secondary_200 = 17170476; // 0x106002c
-    field public static final int system_secondary_300 = 17170477; // 0x106002d
-    field public static final int system_secondary_400 = 17170478; // 0x106002e
-    field public static final int system_secondary_50 = 17170474; // 0x106002a
-    field public static final int system_secondary_500 = 17170479; // 0x106002f
-    field public static final int system_secondary_600 = 17170480; // 0x1060030
-    field public static final int system_secondary_700 = 17170481; // 0x1060031
-    field public static final int system_secondary_800 = 17170482; // 0x1060032
-    field public static final int system_secondary_900 = 17170483; // 0x1060033
+    field public static final int system_accent1_0 = 17170485; // 0x1060035
+    field public static final int system_accent1_100 = 17170487; // 0x1060037
+    field public static final int system_accent1_1000 = 17170496; // 0x1060040
+    field public static final int system_accent1_200 = 17170488; // 0x1060038
+    field public static final int system_accent1_300 = 17170489; // 0x1060039
+    field public static final int system_accent1_400 = 17170490; // 0x106003a
+    field public static final int system_accent1_50 = 17170486; // 0x1060036
+    field public static final int system_accent1_500 = 17170491; // 0x106003b
+    field public static final int system_accent1_600 = 17170492; // 0x106003c
+    field public static final int system_accent1_700 = 17170493; // 0x106003d
+    field public static final int system_accent1_800 = 17170494; // 0x106003e
+    field public static final int system_accent1_900 = 17170495; // 0x106003f
+    field public static final int system_accent2_0 = 17170497; // 0x1060041
+    field public static final int system_accent2_100 = 17170499; // 0x1060043
+    field public static final int system_accent2_1000 = 17170508; // 0x106004c
+    field public static final int system_accent2_200 = 17170500; // 0x1060044
+    field public static final int system_accent2_300 = 17170501; // 0x1060045
+    field public static final int system_accent2_400 = 17170502; // 0x1060046
+    field public static final int system_accent2_50 = 17170498; // 0x1060042
+    field public static final int system_accent2_500 = 17170503; // 0x1060047
+    field public static final int system_accent2_600 = 17170504; // 0x1060048
+    field public static final int system_accent2_700 = 17170505; // 0x1060049
+    field public static final int system_accent2_800 = 17170506; // 0x106004a
+    field public static final int system_accent2_900 = 17170507; // 0x106004b
+    field public static final int system_accent3_0 = 17170509; // 0x106004d
+    field public static final int system_accent3_100 = 17170511; // 0x106004f
+    field public static final int system_accent3_1000 = 17170520; // 0x1060058
+    field public static final int system_accent3_200 = 17170512; // 0x1060050
+    field public static final int system_accent3_300 = 17170513; // 0x1060051
+    field public static final int system_accent3_400 = 17170514; // 0x1060052
+    field public static final int system_accent3_50 = 17170510; // 0x106004e
+    field public static final int system_accent3_500 = 17170515; // 0x1060053
+    field public static final int system_accent3_600 = 17170516; // 0x1060054
+    field public static final int system_accent3_700 = 17170517; // 0x1060055
+    field public static final int system_accent3_800 = 17170518; // 0x1060056
+    field public static final int system_accent3_900 = 17170519; // 0x1060057
+    field public static final int system_neutral1_0 = 17170461; // 0x106001d
+    field public static final int system_neutral1_100 = 17170463; // 0x106001f
+    field public static final int system_neutral1_1000 = 17170472; // 0x1060028
+    field public static final int system_neutral1_200 = 17170464; // 0x1060020
+    field public static final int system_neutral1_300 = 17170465; // 0x1060021
+    field public static final int system_neutral1_400 = 17170466; // 0x1060022
+    field public static final int system_neutral1_50 = 17170462; // 0x106001e
+    field public static final int system_neutral1_500 = 17170467; // 0x1060023
+    field public static final int system_neutral1_600 = 17170468; // 0x1060024
+    field public static final int system_neutral1_700 = 17170469; // 0x1060025
+    field public static final int system_neutral1_800 = 17170470; // 0x1060026
+    field public static final int system_neutral1_900 = 17170471; // 0x1060027
+    field public static final int system_neutral2_0 = 17170473; // 0x1060029
+    field public static final int system_neutral2_100 = 17170475; // 0x106002b
+    field public static final int system_neutral2_1000 = 17170484; // 0x1060034
+    field public static final int system_neutral2_200 = 17170476; // 0x106002c
+    field public static final int system_neutral2_300 = 17170477; // 0x106002d
+    field public static final int system_neutral2_400 = 17170478; // 0x106002e
+    field public static final int system_neutral2_50 = 17170474; // 0x106002a
+    field public static final int system_neutral2_500 = 17170479; // 0x106002f
+    field public static final int system_neutral2_600 = 17170480; // 0x1060030
+    field public static final int system_neutral2_700 = 17170481; // 0x1060031
+    field public static final int system_neutral2_800 = 17170482; // 0x1060032
+    field public static final int system_neutral2_900 = 17170483; // 0x1060033
     field public static final int tab_indicator_text = 17170441; // 0x1060009
     field @Deprecated public static final int tertiary_text_dark = 17170448; // 0x1060010
     field @Deprecated public static final int tertiary_text_light = 17170449; // 0x1060011
@@ -5656,6 +5680,7 @@
     field public static final String EXTRA_PEOPLE_LIST = "android.people.list";
     field public static final String EXTRA_PICTURE = "android.picture";
     field public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION = "android.pictureContentDescription";
+    field public static final String EXTRA_PICTURE_ICON = "android.pictureIcon";
     field public static final String EXTRA_PROGRESS = "android.progress";
     field public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
     field public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
@@ -5804,8 +5829,9 @@
     method @NonNull public android.app.Notification.BigPictureStyle bigLargeIcon(@Nullable android.graphics.Bitmap);
     method @NonNull public android.app.Notification.BigPictureStyle bigLargeIcon(@Nullable android.graphics.drawable.Icon);
     method @NonNull public android.app.Notification.BigPictureStyle bigPicture(@Nullable android.graphics.Bitmap);
-    method @NonNull public android.app.Notification.BigPictureStyle bigPictureContentDescription(@Nullable CharSequence);
+    method @NonNull public android.app.Notification.BigPictureStyle bigPicture(@Nullable android.graphics.drawable.Icon);
     method @NonNull public android.app.Notification.BigPictureStyle setBigContentTitle(@Nullable CharSequence);
+    method @NonNull public android.app.Notification.BigPictureStyle setContentDescription(@Nullable CharSequence);
     method @NonNull public android.app.Notification.BigPictureStyle setSummaryText(@Nullable CharSequence);
     method @NonNull public android.app.Notification.BigPictureStyle showBigPictureWhenCollapsed(boolean);
   }
@@ -7157,6 +7183,7 @@
     method public boolean isCommonCriteriaModeEnabled(@Nullable android.content.ComponentName);
     method public boolean isDeviceIdAttestationSupported();
     method public boolean isDeviceOwnerApp(String);
+    method public boolean isEnterpriseNetworkPreferenceEnabled();
     method public boolean isEphemeralUser(@NonNull android.content.ComponentName);
     method public boolean isKeyPairGrantedToWifiAuth(@NonNull String);
     method public boolean isLockTaskPermitted(String);
@@ -7164,7 +7191,6 @@
     method public boolean isManagedProfile(@NonNull android.content.ComponentName);
     method public boolean isMasterVolumeMuted(@NonNull android.content.ComponentName);
     method public boolean isNetworkLoggingEnabled(@Nullable android.content.ComponentName);
-    method public boolean isNetworkSlicingEnabled();
     method public boolean isOrganizationOwnedDeviceWithManagedProfile();
     method public boolean isOverrideApnEnabled(@NonNull android.content.ComponentName);
     method public boolean isPackageSuspended(@NonNull android.content.ComponentName, String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -7219,6 +7245,7 @@
     method public void setDelegatedScopes(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.lang.String>);
     method public void setDeviceOwnerLockScreenInfo(@NonNull android.content.ComponentName, CharSequence);
     method public void setEndUserSessionMessage(@NonNull android.content.ComponentName, @Nullable CharSequence);
+    method public void setEnterpriseNetworkPreferenceEnabled(boolean);
     method public void setFactoryResetProtectionPolicy(@NonNull android.content.ComponentName, @Nullable android.app.admin.FactoryResetProtectionPolicy);
     method public int setGlobalPrivateDnsModeOpportunistic(@NonNull android.content.ComponentName);
     method @WorkerThread public int setGlobalPrivateDnsModeSpecifiedHost(@NonNull android.content.ComponentName, @NonNull String);
@@ -7238,7 +7265,6 @@
     method public void setMaximumTimeToLock(@NonNull android.content.ComponentName, long);
     method @NonNull public java.util.List<java.lang.String> setMeteredDataDisabledPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>);
     method public void setNetworkLoggingEnabled(@Nullable android.content.ComponentName, boolean);
-    method public void setNetworkSlicingEnabled(boolean);
     method @Deprecated public void setOrganizationColor(@NonNull android.content.ComponentName, int);
     method public void setOrganizationId(@NonNull String);
     method public void setOrganizationName(@NonNull android.content.ComponentName, @Nullable CharSequence);
@@ -7944,6 +7970,7 @@
     method @NonNull public android.os.PersistableBundle getExtras();
     method public int getJobId();
     method @Nullable public android.net.Network getNetwork();
+    method public int getStopReason();
     method @NonNull public android.os.Bundle getTransientExtras();
     method @Nullable public String[] getTriggeredContentAuthorities();
     method @Nullable public android.net.Uri[] getTriggeredContentUris();
@@ -7952,6 +7979,21 @@
     method public boolean isOverrideDeadlineExpired();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.job.JobParameters> CREATOR;
+    field public static final int STOP_REASON_APP_STANDBY = 12; // 0xc
+    field public static final int STOP_REASON_BACKGROUND_RESTRICTION = 11; // 0xb
+    field public static final int STOP_REASON_CANCELLED_BY_APP = 1; // 0x1
+    field public static final int STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW = 5; // 0x5
+    field public static final int STOP_REASON_CONSTRAINT_CHARGING = 6; // 0x6
+    field public static final int STOP_REASON_CONSTRAINT_CONNECTIVITY = 7; // 0x7
+    field public static final int STOP_REASON_CONSTRAINT_DEVICE_IDLE = 8; // 0x8
+    field public static final int STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW = 9; // 0x9
+    field public static final int STOP_REASON_DEVICE_STATE = 4; // 0x4
+    field public static final int STOP_REASON_PREEMPT = 2; // 0x2
+    field public static final int STOP_REASON_QUOTA = 10; // 0xa
+    field public static final int STOP_REASON_SYSTEM_PROCESSING = 14; // 0xe
+    field public static final int STOP_REASON_TIMEOUT = 3; // 0x3
+    field public static final int STOP_REASON_UNDEFINED = 0; // 0x0
+    field public static final int STOP_REASON_USER = 13; // 0xd
   }
 
   public abstract class JobScheduler {
@@ -8967,6 +9009,8 @@
     field public static final String ACTION_NAME_CHANGED = "android.bluetooth.device.action.NAME_CHANGED";
     field public static final String ACTION_PAIRING_REQUEST = "android.bluetooth.device.action.PAIRING_REQUEST";
     field public static final String ACTION_UUID = "android.bluetooth.device.action.UUID";
+    field public static final int ADDRESS_TYPE_PUBLIC = 0; // 0x0
+    field public static final int ADDRESS_TYPE_RANDOM = 1; // 0x1
     field public static final int BOND_BONDED = 12; // 0xc
     field public static final int BOND_BONDING = 11; // 0xb
     field public static final int BOND_NONE = 10; // 0xa
@@ -21297,6 +21341,7 @@
     method public void setCallback(@Nullable android.media.MediaCodec.Callback, @Nullable android.os.Handler);
     method public void setCallback(@Nullable android.media.MediaCodec.Callback);
     method public void setInputSurface(@NonNull android.view.Surface);
+    method public void setOnFirstTunnelFrameReadyListener(@Nullable android.os.Handler, @Nullable android.media.MediaCodec.OnFirstTunnelFrameReadyListener);
     method public void setOnFrameRenderedListener(@Nullable android.media.MediaCodec.OnFrameRenderedListener, @Nullable android.os.Handler);
     method public void setOutputSurface(@NonNull android.view.Surface);
     method public void setParameters(@Nullable android.os.Bundle);
@@ -21324,6 +21369,7 @@
     field public static final String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync";
     field public static final String PARAMETER_KEY_SUSPEND = "drop-input-frames";
     field public static final String PARAMETER_KEY_SUSPEND_TIME = "drop-start-time-us";
+    field public static final String PARAMETER_KEY_TUNNEL_PEEK = "tunnel-peek";
     field public static final String PARAMETER_KEY_VIDEO_BITRATE = "video-bitrate";
     field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; // 0x1
     field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; // 0x2
@@ -21414,6 +21460,10 @@
     field public static final String WIDTH = "android.media.mediacodec.width";
   }
 
+  public static interface MediaCodec.OnFirstTunnelFrameReadyListener {
+    method public void onFirstTunnelFrameReady(@NonNull android.media.MediaCodec);
+  }
+
   public static interface MediaCodec.OnFrameRenderedListener {
     method public void onFrameRendered(@NonNull android.media.MediaCodec, long, long);
   }
@@ -39159,7 +39209,7 @@
     method public android.telecom.Call getParent();
     method public String getRemainingPostDialSequence();
     method @Nullable public android.telecom.Call.RttCall getRttCall();
-    method public int getState();
+    method @Deprecated public int getState();
     method public android.telecom.InCallService.VideoCall getVideoCall();
     method public void handoverTo(android.telecom.PhoneAccountHandle, int, android.os.Bundle);
     method public void hold();
@@ -39254,6 +39304,7 @@
     method public android.net.Uri getHandle();
     method public int getHandlePresentation();
     method public android.os.Bundle getIntentExtras();
+    method public final int getState();
     method public android.telecom.StatusHints getStatusHints();
     method public int getVideoState();
     method public static boolean hasProperty(int, int);
@@ -42099,7 +42150,7 @@
     method public static int getDefaultSmsSubscriptionId();
     method public static int getDefaultSubscriptionId();
     method public static int getDefaultVoiceSubscriptionId();
-    method public int getDeviceToDeviceStatusSharing(int);
+    method public int getDeviceToDeviceStatusSharingPreference(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telephony.SubscriptionInfo> getOpportunisticSubscriptions();
     method public static int getSlotIndex(int);
     method @Nullable public int[] getSubscriptionIds(int);
@@ -42112,7 +42163,7 @@
     method public void removeOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
     method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void removeSubscriptionsFromGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDeviceToDeviceStatusSharing(int, int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDeviceToDeviceStatusSharingPreference(int, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunistic(boolean, int);
     method public void setSubscriptionOverrideCongested(int, boolean, long);
     method public void setSubscriptionOverrideCongested(int, boolean, @NonNull int[], long);
@@ -42335,6 +42386,7 @@
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getNetworkSlicingConfiguration(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.data.SlicingConfig,android.telephony.TelephonyManager.SlicingException>);
     method public String getNetworkSpecifier();
     method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getNetworkType();
+    method @Nullable @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
     method @Deprecated public int getPhoneCount();
     method public int getPhoneType();
     method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription();
@@ -42371,6 +42423,7 @@
     method @Deprecated public String iccTransmitApduBasicChannel(int, int, int, int, int, String);
     method @Deprecated public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String);
     method public boolean isConcurrentVoiceAndDataSupported();
+    method public boolean isDataCapable();
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isDataConnectionAllowed();
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabled();
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabledForReason(int);
@@ -52476,9 +52529,43 @@
 
 package android.view.translation {
 
+  public final class TranslationCapability implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.view.translation.TranslationSpec getSourceSpec();
+    method public int getState();
+    method public int getSupportedTranslationFlags();
+    method @NonNull public android.view.translation.TranslationSpec getTargetSpec();
+    method public boolean isUiTranslationEnabled();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.view.translation.TranslationCapability> CREATOR;
+    field public static final int STATE_AVAILABLE_TO_DOWNLOAD = 1; // 0x1
+    field public static final int STATE_DOWNLOADING = 2; // 0x2
+    field public static final int STATE_ON_DEVICE = 3; // 0x3
+  }
+
+  public final class TranslationContext implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.view.translation.TranslationSpec getSourceSpec();
+    method @NonNull public android.view.translation.TranslationSpec getTargetSpec();
+    method public int getTranslationFlags();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.view.translation.TranslationContext> CREATOR;
+    field public static final int FLAG_DICTIONARY_DESCRIPTION = 4; // 0x4
+    field public static final int FLAG_LOW_LATENCY = 1; // 0x1
+    field public static final int FLAG_TRANSLITERATION = 2; // 0x2
+  }
+
+  public static final class TranslationContext.Builder {
+    ctor public TranslationContext.Builder(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec);
+    method @NonNull public android.view.translation.TranslationContext build();
+    method @NonNull public android.view.translation.TranslationContext.Builder setTranslationFlags(int);
+  }
+
   public final class TranslationManager {
-    method @Nullable @WorkerThread public android.view.translation.Translator createTranslator(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec);
-    method @NonNull @WorkerThread public java.util.List<java.lang.String> getSupportedLocales();
+    method public void addTranslationCapabilityUpdateListener(int, int, @NonNull android.app.PendingIntent);
+    method @Nullable @WorkerThread public android.view.translation.Translator createTranslator(@NonNull android.view.translation.TranslationContext);
+    method @NonNull @WorkerThread public java.util.Set<android.view.translation.TranslationCapability> getTranslationCapabilities(int, int);
+    method public void removeTranslationCapabilityUpdateListener(int, int, @NonNull android.app.PendingIntent);
   }
 
   public final class TranslationRequest implements android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 13b83796..a0e0f71 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -65,6 +65,7 @@
     field public static final String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
     field public static final String BRICK = "android.permission.BRICK";
     field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
+    field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS";
     field @Deprecated public static final String BROADCAST_NETWORK_PRIVILEGED = "android.permission.BROADCAST_NETWORK_PRIVILEGED";
     field public static final String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED";
     field public static final String CAMERA_OPEN_CLOSE_LISTENER = "android.permission.CAMERA_OPEN_CLOSE_LISTENER";
@@ -698,6 +699,9 @@
     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);
+    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 class Notification implements android.os.Parcelable {
@@ -1191,12 +1195,14 @@
 
   public class RestoreSet implements android.os.Parcelable {
     ctor public RestoreSet();
-    ctor public RestoreSet(String, String, long);
+    ctor public RestoreSet(@Nullable String, @Nullable String, long);
+    ctor public RestoreSet(@Nullable String, @Nullable String, long, int);
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.backup.RestoreSet> CREATOR;
-    field public String device;
-    field public String name;
+    field public final int backupTransportFlags;
+    field @Nullable public String device;
+    field @Nullable public String name;
     field public long token;
   }
 
@@ -1832,6 +1838,7 @@
 package android.apphibernation {
 
   public final class AppHibernationManager {
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public java.util.List<java.lang.String> getHibernatingPackagesForUser();
     method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public boolean isHibernatingForUser(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public boolean isHibernatingGlobally(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void setHibernatingForUser(@NonNull String, boolean);
@@ -2138,7 +2145,19 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.ResultStorageDescriptor> CREATOR;
   }
 
+  public final class ScanFilter implements android.os.Parcelable {
+    method public int getAddressType();
+    method @Nullable public byte[] getIrk();
+  }
+
+  public static final class ScanFilter.Builder {
+    method @NonNull public android.bluetooth.le.ScanFilter.Builder setDeviceAddress(@NonNull String, int);
+    method @NonNull public android.bluetooth.le.ScanFilter.Builder setDeviceAddress(@NonNull String, int, @NonNull byte[]);
+    field public static final int LEN_IRK_OCTETS = 16; // 0x10
+  }
+
   public final class ScanSettings implements android.os.Parcelable {
+    field public static final int SCAN_MODE_AMBIENT_DISCOVERY = 3; // 0x3
     field public static final int SCAN_RESULT_TYPE_ABBREVIATED = 1; // 0x1
     field public static final int SCAN_RESULT_TYPE_FULL = 0; // 0x0
   }
@@ -5212,6 +5231,7 @@
     method @Nullable public String getClientPackageName();
     method @Nullable public android.media.MediaRouter2.RoutingController getController(@NonNull String);
     method @Nullable public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull String);
+    method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback);
     method public void setRouteVolume(@NonNull android.media.MediaRoute2Info, int);
     method public void startScan();
     method public void stopScan();
@@ -8254,9 +8274,10 @@
   public class PowerExemptionManager {
     method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToPermanentAllowList(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToPermanentAllowList(@NonNull java.util.List<java.lang.String>);
-    method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void addToTemporaryAllowList(@NonNull String, long, int, @Nullable String);
-    method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long addToTemporaryAllowListForEvent(@NonNull String, int, int, @Nullable String);
-    method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void removeFromAllowList(@NonNull String);
+    method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void addToTemporaryAllowList(@NonNull String, int, @Nullable String, long);
+    method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long addToTemporaryAllowListForEvent(@NonNull String, int, @Nullable String, int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public int[] getAllowListedAppIds(boolean);
+    method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void removeFromPermanentAllowList(@NonNull String);
     field public static final int EVENT_MMS = 2; // 0x2
     field public static final int EVENT_SMS = 1; // 0x1
     field public static final int EVENT_UNSPECIFIED = 0; // 0x0
@@ -8480,6 +8501,7 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean hasRestrictedProfiles();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean hasUserRestrictionForUser(@NonNull String, @NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isAdminUser();
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isCloneProfile();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isGuestUser();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isManagedProfile(int);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isPrimaryUser();
@@ -8493,6 +8515,7 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap) throws android.os.UserManager.UserOperationException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserName(@Nullable String);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean sharesMediaWithParent();
     field public static final String ACTION_USER_RESTRICTIONS_CHANGED = "android.os.action.USER_RESTRICTIONS_CHANGED";
     field @Deprecated public static final String DISALLOW_OEM_UNLOCK = "no_oem_unlock";
     field public static final String DISALLOW_RUN_IN_BACKGROUND = "no_run_in_background";
@@ -8506,6 +8529,7 @@
     field public static final int SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED = 2; // 0x2
     field public static final String USER_TYPE_FULL_SECONDARY = "android.os.usertype.full.SECONDARY";
     field public static final String USER_TYPE_FULL_SYSTEM = "android.os.usertype.full.SYSTEM";
+    field public static final String USER_TYPE_PROFILE_CLONE = "android.os.usertype.profile.CLONE";
     field public static final String USER_TYPE_PROFILE_MANAGED = "android.os.usertype.profile.MANAGED";
     field public static final String USER_TYPE_SYSTEM_HEADLESS = "android.os.usertype.system.HEADLESS";
   }
@@ -10243,9 +10267,10 @@
     ctor public TranslationService();
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method public void onConnected();
-    method public abstract void onCreateTranslationSession(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, int);
+    method public abstract void onCreateTranslationSession(@NonNull android.view.translation.TranslationContext, int);
     method public void onDisconnected();
     method public abstract void onFinishTranslationSession(int);
+    method public abstract void onTranslationCapabilitiesRequest(int, int, @NonNull java.util.function.Consumer<java.util.Set<android.view.translation.TranslationCapability>>);
     method public abstract void onTranslationRequest(@NonNull android.view.translation.TranslationRequest, int, @NonNull android.os.CancellationSignal, @NonNull android.service.translation.TranslationService.OnTranslationResultCallback);
     field public static final String SERVICE_INTERFACE = "android.service.translation.TranslationService";
     field public static final String SERVICE_META_DATA = "android.translation_service";
@@ -11055,7 +11080,8 @@
 
   public final class DataSpecificRegistrationInfo implements android.os.Parcelable {
     method public int describeContents();
-    method @NonNull public android.telephony.LteVopsSupportInfo getLteVopsSupportInfo();
+    method @Deprecated @NonNull public android.telephony.LteVopsSupportInfo getLteVopsSupportInfo();
+    method @Nullable public android.telephony.VopsSupportInfo getVopsSupportInfo();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.DataSpecificRegistrationInfo> CREATOR;
   }
@@ -11100,14 +11126,16 @@
     field public static final int LCE_TYPE_SECONDARY = 1; // 0x1
   }
 
-  public final class LteVopsSupportInfo implements android.os.Parcelable {
+  public final class LteVopsSupportInfo extends android.telephony.VopsSupportInfo {
     ctor public LteVopsSupportInfo(int, int);
-    method public int describeContents();
     method public int getEmcBearerSupport();
     method public int getVopsSupport();
-    method public void writeToParcel(android.os.Parcel, int);
+    method public boolean isEmergencyServiceFallbackSupported();
+    method public boolean isEmergencyServiceSupported();
+    method public boolean isVopsSupported();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.LteVopsSupportInfo> CREATOR;
-    field public static final int LTE_STATUS_NOT_AVAILABLE = 1; // 0x1
+    field @Deprecated public static final int LTE_STATUS_NOT_AVAILABLE = 1; // 0x1
     field public static final int LTE_STATUS_NOT_SUPPORTED = 3; // 0x3
     field public static final int LTE_STATUS_SUPPORTED = 2; // 0x2
   }
@@ -11197,6 +11225,29 @@
     field public static final int RESULT_SUCCESS = 0; // 0x0
   }
 
+  public final class NrVopsSupportInfo extends android.telephony.VopsSupportInfo {
+    ctor public NrVopsSupportInfo(int, int, int);
+    method public int getEmcSupport();
+    method public int getEmfSupport();
+    method public int getVopsSupport();
+    method public boolean isEmergencyServiceFallbackSupported();
+    method public boolean isEmergencyServiceSupported();
+    method public boolean isVopsSupported();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.NrVopsSupportInfo> CREATOR;
+    field public static final int NR_STATUS_EMC_5GCN_ONLY = 1; // 0x1
+    field public static final int NR_STATUS_EMC_EUTRA_5GCN_ONLY = 2; // 0x2
+    field public static final int NR_STATUS_EMC_NOT_SUPPORTED = 0; // 0x0
+    field public static final int NR_STATUS_EMC_NR_EUTRA_5GCN = 3; // 0x3
+    field public static final int NR_STATUS_EMF_5GCN_ONLY = 1; // 0x1
+    field public static final int NR_STATUS_EMF_EUTRA_5GCN_ONLY = 2; // 0x2
+    field public static final int NR_STATUS_EMF_NOT_SUPPORTED = 0; // 0x0
+    field public static final int NR_STATUS_EMF_NR_EUTRA_5GCN = 3; // 0x3
+    field public static final int NR_STATUS_VOPS_3GPP_SUPPORTED = 1; // 0x1
+    field public static final int NR_STATUS_VOPS_NON_3GPP_SUPPORTED = 2; // 0x2
+    field public static final int NR_STATUS_VOPS_NOT_SUPPORTED = 0; // 0x0
+  }
+
   public interface NumberVerificationCallback {
     method public default void onCallReceived(@NonNull String);
     method public default void onVerificationFailed(int);
@@ -11862,6 +11913,7 @@
     field public static final String CAPABILITY_ALLOWED_NETWORK_TYPES_USED = "CAPABILITY_ALLOWED_NETWORK_TYPES_USED";
     field public static final String CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE = "CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE";
     field public static final String CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE = "CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE";
+    field public static final String CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING = "CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING";
     field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
     field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
     field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
@@ -12023,6 +12075,16 @@
     method public static final void setSmsFilterSettings(android.content.Context, android.telecom.PhoneAccountHandle, android.telephony.VisualVoicemailSmsFilterSettings);
   }
 
+  public abstract class VopsSupportInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public abstract boolean equals(Object);
+    method public abstract int hashCode();
+    method public abstract boolean isEmergencyServiceFallbackSupported();
+    method public abstract boolean isEmergencyServiceSupported();
+    method public abstract boolean isVopsSupported();
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.VopsSupportInfo> CREATOR;
+  }
+
 }
 
 package android.telephony.cdma {
@@ -13240,6 +13302,7 @@
     method @Nullable public android.telephony.ims.RcsContactPresenceTuple getCapabilityTuple(@NonNull String);
     method @NonNull public java.util.List<android.telephony.ims.RcsContactPresenceTuple> getCapabilityTuples();
     method @NonNull public android.net.Uri getContactUri();
+    method @NonNull public java.util.Set<java.lang.String> getFeatureTags();
     method public int getRequestResult();
     method public int getSourceType();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -13254,6 +13317,14 @@
     field public static final int SOURCE_TYPE_NETWORK = 0; // 0x0
   }
 
+  public static final class RcsContactUceCapability.OptionsBuilder {
+    ctor public RcsContactUceCapability.OptionsBuilder(@NonNull android.net.Uri);
+    method @NonNull public android.telephony.ims.RcsContactUceCapability.OptionsBuilder addFeatureTag(@NonNull String);
+    method @NonNull public android.telephony.ims.RcsContactUceCapability.OptionsBuilder addFeatureTags(@NonNull java.util.Set<java.lang.String>);
+    method @NonNull public android.telephony.ims.RcsContactUceCapability build();
+    method @NonNull public android.telephony.ims.RcsContactUceCapability.OptionsBuilder setRequestResult(int);
+  }
+
   public static final class RcsContactUceCapability.PresenceBuilder {
     ctor public RcsContactUceCapability.PresenceBuilder(@NonNull android.net.Uri, int, int);
     method @NonNull public android.telephony.ims.RcsContactUceCapability.PresenceBuilder addCapabilityTuple(@NonNull android.telephony.ims.RcsContactPresenceTuple);
@@ -13547,7 +13618,7 @@
 package android.telephony.ims.stub {
 
   public interface CapabilityExchangeEventListener {
-    method public void onRemoteCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.List<java.lang.String>, @NonNull android.telephony.ims.stub.CapabilityExchangeEventListener.OptionsRequestCallback) throws android.telephony.ims.ImsException;
+    method public void onRemoteCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.Set<java.lang.String>, @NonNull android.telephony.ims.stub.CapabilityExchangeEventListener.OptionsRequestCallback) throws android.telephony.ims.ImsException;
     method public void onRequestPublishCapabilities(int) throws android.telephony.ims.ImsException;
     method public void onUnpublish() throws android.telephony.ims.ImsException;
   }
@@ -13744,7 +13815,7 @@
   public class RcsCapabilityExchangeImplBase {
     ctor public RcsCapabilityExchangeImplBase(@NonNull java.util.concurrent.Executor);
     method public void publishCapabilities(@NonNull String, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.PublishResponseCallback);
-    method public void sendOptionsCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.List<java.lang.String>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.OptionsResponseCallback);
+    method public void sendOptionsCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.Set<java.lang.String>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.OptionsResponseCallback);
     method public void subscribeForCapabilities(@NonNull java.util.Collection<android.net.Uri>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.SubscribeResponseCallback);
     field public static final int COMMAND_CODE_FETCH_ERROR = 3; // 0x3
     field public static final int COMMAND_CODE_GENERIC_FAILURE = 1; // 0x1
@@ -14204,6 +14275,10 @@
 
 package android.view.translation {
 
+  public final class TranslationCapability implements android.os.Parcelable {
+    ctor public TranslationCapability(int, @NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, boolean, int);
+  }
+
   public final class UiTranslationManager {
     method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(@NonNull android.app.assist.ActivityId);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 97e107c..7bc1993 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -29,7 +29,6 @@
     field public static final String NETWORK_STACK = "android.permission.NETWORK_STACK";
     field public static final String OVERRIDE_DISPLAY_MODE_REQUESTS = "android.permission.OVERRIDE_DISPLAY_MODE_REQUESTS";
     field public static final String QUERY_AUDIO_STATE = "android.permission.QUERY_AUDIO_STATE";
-    field public static final String QUERY_USERS = "android.permission.QUERY_USERS";
     field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
     field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE";
     field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
@@ -107,7 +106,6 @@
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean);
     method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle();
-    field public static final long DROP_CLOSE_SYSTEM_DIALOGS = 174664120L; // 0xa6929b8L
     field public static final long LOCK_DOWN_CLOSE_SYSTEM_DIALOGS = 174664365L; // 0xa692aadL
     field public static final int PROCESS_CAPABILITY_ALL = 15; // 0xf
     field public static final int PROCESS_CAPABILITY_ALL_EXPLICIT = 1; // 0x1
@@ -819,6 +817,7 @@
     method public int describeContents();
     method public android.os.UserHandle getUserHandle();
     method public boolean isAdmin();
+    method public boolean isCloneProfile();
     method public boolean isDemo();
     method public boolean isEnabled();
     method public boolean isEphemeral();
@@ -1713,35 +1712,16 @@
     field public static final int[] RINGTONES;
   }
 
-  public static class VibrationEffect.OneShot extends android.os.VibrationEffect implements android.os.Parcelable {
-    ctor public VibrationEffect.OneShot(android.os.Parcel);
-    ctor public VibrationEffect.OneShot(long, int);
-    method public int getAmplitude();
-    method public long getDuration();
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.OneShot> CREATOR;
-  }
-
-  public static class VibrationEffect.Prebaked extends android.os.VibrationEffect implements android.os.Parcelable {
-    ctor public VibrationEffect.Prebaked(android.os.Parcel);
-    ctor public VibrationEffect.Prebaked(int, boolean, int);
-    method public long getDuration();
-    method public int getEffectStrength();
-    method public int getId();
-    method public boolean shouldFallback();
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.Prebaked> CREATOR;
-  }
-
-  public static class VibrationEffect.Waveform extends android.os.VibrationEffect implements android.os.Parcelable {
-    ctor public VibrationEffect.Waveform(android.os.Parcel);
-    ctor public VibrationEffect.Waveform(long[], int[], int);
-    method public int[] getAmplitudes();
+  public static final class VibrationEffect.Composed extends android.os.VibrationEffect {
+    method @NonNull public android.os.VibrationEffect.Composed applyEffectStrength(int);
     method public long getDuration();
     method public int getRepeatIndex();
-    method public long[] getTimings();
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.Waveform> CREATOR;
+    method @NonNull public java.util.List<android.os.vibrator.VibrationEffectSegment> getSegments();
+    method @NonNull public android.os.VibrationEffect.Composed resolve(int);
+    method @NonNull public android.os.VibrationEffect.Composed scale(float);
+    method public void validate();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.Composed> CREATOR;
   }
 
   public class VintfObject {
@@ -1858,6 +1838,63 @@
 
 }
 
+package android.os.vibrator {
+
+  public final class PrebakedSegment extends android.os.vibrator.VibrationEffectSegment {
+    method @NonNull public android.os.vibrator.PrebakedSegment applyEffectStrength(int);
+    method public int describeContents();
+    method public long getDuration();
+    method public int getEffectId();
+    method public int getEffectStrength();
+    method public boolean hasNonZeroAmplitude();
+    method @NonNull public android.os.vibrator.PrebakedSegment resolve(int);
+    method @NonNull public android.os.vibrator.PrebakedSegment scale(float);
+    method public boolean shouldFallback();
+    method public void validate();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.PrebakedSegment> CREATOR;
+  }
+
+  public final class PrimitiveSegment extends android.os.vibrator.VibrationEffectSegment {
+    method @NonNull public android.os.vibrator.PrimitiveSegment applyEffectStrength(int);
+    method public int describeContents();
+    method public int getDelay();
+    method public long getDuration();
+    method public int getPrimitiveId();
+    method public float getScale();
+    method public boolean hasNonZeroAmplitude();
+    method @NonNull public android.os.vibrator.PrimitiveSegment resolve(int);
+    method @NonNull public android.os.vibrator.PrimitiveSegment scale(float);
+    method public void validate();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.PrimitiveSegment> CREATOR;
+  }
+
+  public final class StepSegment extends android.os.vibrator.VibrationEffectSegment {
+    method @NonNull public android.os.vibrator.StepSegment applyEffectStrength(int);
+    method public int describeContents();
+    method public float getAmplitude();
+    method public long getDuration();
+    method public boolean hasNonZeroAmplitude();
+    method @NonNull public android.os.vibrator.StepSegment resolve(int);
+    method @NonNull public android.os.vibrator.StepSegment scale(float);
+    method public void validate();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.StepSegment> CREATOR;
+  }
+
+  public abstract class VibrationEffectSegment implements android.os.Parcelable {
+    method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T applyEffectStrength(int);
+    method public abstract long getDuration();
+    method public abstract boolean hasNonZeroAmplitude();
+    method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T resolve(int);
+    method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T scale(float);
+    method public abstract void validate();
+    field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.VibrationEffectSegment> CREATOR;
+  }
+
+}
+
 package android.permission {
 
   public final class PermissionControllerManager {
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index a536efb..1833ed5 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -1515,30 +1515,6 @@
     
 MissingNullability: android.os.VibrationEffect#get(int, boolean):
     
-MissingNullability: android.os.VibrationEffect.OneShot#OneShot(android.os.Parcel) parameter #0:
-    
-MissingNullability: android.os.VibrationEffect.OneShot#scale(float, int):
-    
-MissingNullability: android.os.VibrationEffect.OneShot#writeToParcel(android.os.Parcel, int) parameter #0:
-    
-MissingNullability: android.os.VibrationEffect.Prebaked#Prebaked(android.os.Parcel) parameter #0:
-    
-MissingNullability: android.os.VibrationEffect.Prebaked#writeToParcel(android.os.Parcel, int) parameter #0:
-    
-MissingNullability: android.os.VibrationEffect.Waveform#Waveform(android.os.Parcel) parameter #0:
-    
-MissingNullability: android.os.VibrationEffect.Waveform#Waveform(long[], int[], int) parameter #0:
-    
-MissingNullability: android.os.VibrationEffect.Waveform#Waveform(long[], int[], int) parameter #1:
-    
-MissingNullability: android.os.VibrationEffect.Waveform#getAmplitudes():
-    
-MissingNullability: android.os.VibrationEffect.Waveform#getTimings():
-    
-MissingNullability: android.os.VibrationEffect.Waveform#scale(float, int):
-    
-MissingNullability: android.os.VibrationEffect.Waveform#writeToParcel(android.os.Parcel, int) parameter #0:
-    
 MissingNullability: android.os.VintfObject#getHalNamesAndVersions():
     
 MissingNullability: android.os.VintfObject#getSepolicyVersion():
@@ -2739,12 +2715,6 @@
     
 ParcelConstructor: android.os.StrictMode.ViolationInfo#ViolationInfo(android.os.Parcel):
     
-ParcelConstructor: android.os.VibrationEffect.OneShot#OneShot(android.os.Parcel):
-    
-ParcelConstructor: android.os.VibrationEffect.Prebaked#Prebaked(android.os.Parcel):
-    
-ParcelConstructor: android.os.VibrationEffect.Waveform#Waveform(android.os.Parcel):
-    
 ParcelConstructor: android.os.health.HealthStatsParceler#HealthStatsParceler(android.os.Parcel):
     
 ParcelConstructor: android.service.notification.SnoozeCriterion#SnoozeCriterion(android.os.Parcel):
@@ -2773,12 +2743,6 @@
     
 ParcelCreator: android.net.metrics.ValidationProbeEvent:
     
-ParcelCreator: android.os.VibrationEffect.OneShot:
-    
-ParcelCreator: android.os.VibrationEffect.Prebaked:
-    
-ParcelCreator: android.os.VibrationEffect.Waveform:
-    
 ParcelCreator: android.service.autofill.InternalOnClickAction:
     
 ParcelCreator: android.service.autofill.InternalSanitizer:
@@ -2797,12 +2761,6 @@
     
 ParcelNotFinal: android.os.IncidentManager.IncidentReport:
     
-ParcelNotFinal: android.os.VibrationEffect.OneShot:
-    
-ParcelNotFinal: android.os.VibrationEffect.Prebaked:
-    
-ParcelNotFinal: android.os.VibrationEffect.Waveform:
-    
 ParcelNotFinal: android.os.health.HealthStatsParceler:
     
 ParcelNotFinal: android.service.autofill.InternalOnClickAction:
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 3fedda3..36c66e5 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -901,7 +901,6 @@
      *
      * @hide
      */
-    @TestApi
     @ChangeId
     public static final long DROP_CLOSE_SYSTEM_DIALOGS = 174664120L;
 
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index a1dce77..006b2b5 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1606,7 +1606,8 @@
 
     /**
      * Sets a launch cookie that can be used to track the activity and task that are launch as a
-     * result of this option.
+     * result of this option. If the launched activity is a trampoline that starts another activity
+     * immediately, the cookie will be transferred to the next activity.
      *
      * @hide
      */
diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java
index 146d648..618eda8 100644
--- a/core/java/android/app/Application.java
+++ b/core/java/android/app/Application.java
@@ -22,6 +22,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentCallbacks;
 import android.content.ComponentCallbacks2;
+import android.content.ComponentCallbacksController;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
@@ -53,14 +54,14 @@
 public class Application extends ContextWrapper implements ComponentCallbacks2 {
     private static final String TAG = "Application";
     @UnsupportedAppUsage
-    private ArrayList<ComponentCallbacks> mComponentCallbacks =
-            new ArrayList<ComponentCallbacks>();
-    @UnsupportedAppUsage
     private ArrayList<ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =
             new ArrayList<ActivityLifecycleCallbacks>();
     @UnsupportedAppUsage
     private ArrayList<OnProvideAssistDataListener> mAssistCallbacks = null;
 
+    private final ComponentCallbacksController mCallbacksController =
+            new ComponentCallbacksController();
+
     /** @hide */
     @UnsupportedAppUsage
     public LoadedApk mLoadedApk;
@@ -260,47 +261,25 @@
 
     @CallSuper
     public void onConfigurationChanged(@NonNull Configuration newConfig) {
-        Object[] callbacks = collectComponentCallbacks();
-        if (callbacks != null) {
-            for (int i=0; i<callbacks.length; i++) {
-                ((ComponentCallbacks)callbacks[i]).onConfigurationChanged(newConfig);
-            }
-        }
+        mCallbacksController.dispatchConfigurationChanged(newConfig);
     }
 
     @CallSuper
     public void onLowMemory() {
-        Object[] callbacks = collectComponentCallbacks();
-        if (callbacks != null) {
-            for (int i=0; i<callbacks.length; i++) {
-                ((ComponentCallbacks)callbacks[i]).onLowMemory();
-            }
-        }
+        mCallbacksController.dispatchLowMemory();
     }
 
     @CallSuper
     public void onTrimMemory(int level) {
-        Object[] callbacks = collectComponentCallbacks();
-        if (callbacks != null) {
-            for (int i=0; i<callbacks.length; i++) {
-                Object c = callbacks[i];
-                if (c instanceof ComponentCallbacks2) {
-                    ((ComponentCallbacks2)c).onTrimMemory(level);
-                }
-            }
-        }
+        mCallbacksController.dispatchTrimMemory(level);
     }
 
     public void registerComponentCallbacks(ComponentCallbacks callback) {
-        synchronized (mComponentCallbacks) {
-            mComponentCallbacks.add(callback);
-        }
+        mCallbacksController.registerCallbacks(callback);
     }
 
     public void unregisterComponentCallbacks(ComponentCallbacks callback) {
-        synchronized (mComponentCallbacks) {
-            mComponentCallbacks.remove(callback);
-        }
+        mCallbacksController.unregisterCallbacks(callback);
     }
 
     public void registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) {
@@ -575,16 +554,6 @@
         }
     }
 
-    private Object[] collectComponentCallbacks() {
-        Object[] callbacks = null;
-        synchronized (mComponentCallbacks) {
-            if (mComponentCallbacks.size() > 0) {
-                callbacks = mComponentCallbacks.toArray();
-            }
-        }
-        return callbacks;
-    }
-
     @UnsupportedAppUsage
     private Object[] collectActivityLifecycleCallbacks() {
         Object[] callbacks = null;
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 3bfddf7..e5a969a 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -244,6 +244,8 @@
     boolean requestAutofillData(in IAssistDataReceiver receiver, in Bundle receiverExtras,
             in IBinder activityToken, int flags);
     boolean isAssistDataAllowedOnCurrentActivity();
+    boolean requestAssistDataForTask(in IAssistDataReceiver receiver, int taskId,
+            in String callingPackageName);
 
     /**
      * Notify the system that the keyguard is going away.
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index b6d25cf..4326c2d 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -133,6 +133,42 @@
      */
     public static final String EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS = "check_dpm";
 
+    /**
+     *
+     * Password lock type, see {@link #setLock}
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int PASSWORD = 0;
+
+    /**
+     *
+     * Pin lock type, see {@link #setLock}
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int PIN = 1;
+
+    /**
+     *
+     * Pattern lock type, see {@link #setLock}
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int PATTERN = 2;
+
+    /**
+     * Available lock types
+     */
+    @IntDef({
+            PASSWORD,
+            PIN,
+            PATTERN
+    })
+    @interface LockTypes {}
 
     /**
      * Get an intent to prompt the user to confirm credentials (pin, pattern, password or biometrics
@@ -695,7 +731,7 @@
         PasswordMetrics adminMetrics =
                 devicePolicyManager.getPasswordMinimumMetrics(mContext.getUserId());
         // Check if the password fits the mold of a pin or pattern.
-        boolean isPinOrPattern = lockType != LockTypes.PASSWORD;
+        boolean isPinOrPattern = lockType != PASSWORD;
 
         return PasswordMetrics.validatePassword(
                 adminMetrics, complexity, isPinOrPattern, password).size() == 0;
@@ -759,7 +795,7 @@
         boolean success = false;
         try {
             switch (lockType) {
-                case LockTypes.PASSWORD:
+                case PASSWORD:
                     CharSequence passwordStr = new String(password, Charset.forName("UTF-8"));
                     lockPatternUtils.setLockCredential(
                             LockscreenCredential.createPassword(passwordStr),
@@ -767,7 +803,7 @@
                             userId);
                     success = true;
                     break;
-                case LockTypes.PIN:
+                case PIN:
                     CharSequence pinStr = new String(password);
                     lockPatternUtils.setLockCredential(
                             LockscreenCredential.createPin(pinStr),
@@ -775,7 +811,7 @@
                             userId);
                     success = true;
                     break;
-                case LockTypes.PATTERN:
+                case PATTERN:
                     List<LockPatternView.Cell> pattern =
                             LockPatternUtils.byteArrayToPattern(password);
                     lockPatternUtils.setLockCredential(
@@ -796,18 +832,4 @@
         }
         return success;
     }
-
-    /**
-    * Available lock types
-    */
-    @IntDef({
-            LockTypes.PASSWORD,
-            LockTypes.PIN,
-            LockTypes.PATTERN
-    })
-    @interface LockTypes {
-        int PASSWORD = 0;
-        int PIN = 1;
-        int PATTERN = 2;
-    }
 }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index f4e214c..4eda6fe 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -20,8 +20,6 @@
 import static android.graphics.drawable.Icon.TYPE_URI;
 import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
 
-import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast;
-
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.AttrRes;
@@ -1207,9 +1205,16 @@
     public static final String EXTRA_PICTURE = "android.picture";
 
     /**
+     * {@link #extras} key: this is an {@link Icon} of an image to be
+     * shown in {@link BigPictureStyle} expanded notifications, supplied to
+     * {@link BigPictureStyle#bigPicture(Icon)}.
+     */
+    public static final String EXTRA_PICTURE_ICON = "android.pictureIcon";
+
+    /**
      * {@link #extras} key: this is a content description of the big picture supplied from
      * {@link BigPictureStyle#bigPicture(Bitmap)}, supplied to
-     * {@link BigPictureStyle#bigPictureContentDescription(CharSequence)}.
+     * {@link BigPictureStyle#setContentDescription(CharSequence)}.
      */
     public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION =
             "android.pictureContentDescription";
@@ -2668,6 +2673,14 @@
         }
     }
 
+    private static void visitIconUri(@NonNull Consumer<Uri> visitor, @Nullable Icon icon) {
+        if (icon == null) return;
+        final int iconType = icon.getType();
+        if (iconType == TYPE_URI || iconType == TYPE_URI_ADAPTIVE_BITMAP) {
+            visitor.accept(icon.getUri());
+        }
+    }
+
     /**
      * Note all {@link Uri} that are referenced internally, with the expectation
      * that Uri permission grants will need to be issued to ensure the recipient
@@ -2683,7 +2696,19 @@
         if (bigContentView != null) bigContentView.visitUris(visitor);
         if (headsUpContentView != null) headsUpContentView.visitUris(visitor);
 
+        visitIconUri(visitor, mSmallIcon);
+        visitIconUri(visitor, mLargeIcon);
+
+        if (actions != null) {
+            for (Action action : actions) {
+                visitIconUri(visitor, action.getIcon());
+            }
+        }
+
         if (extras != null) {
+            visitIconUri(visitor, extras.getParcelable(EXTRA_LARGE_ICON_BIG));
+            visitIconUri(visitor, extras.getParcelable(EXTRA_PICTURE_ICON));
+
             // NOTE: The documentation of EXTRA_AUDIO_CONTENTS_URI explicitly says that it is a
             // String representation of a Uri, but the previous implementation (and unit test) of
             // this method has always treated it as a Uri object. Given the inconsistency,
@@ -2702,14 +2727,12 @@
             ArrayList<Person> people = extras.getParcelableArrayList(EXTRA_PEOPLE_LIST);
             if (people != null && !people.isEmpty()) {
                 for (Person p : people) {
-                    if (p.getIconUri() != null) {
-                        visitor.accept(p.getIconUri());
-                    }
+                    visitor.accept(p.getIconUri());
                 }
             }
 
             final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON);
-            if (person != null && person.getIconUri() != null) {
+            if (person != null) {
                 visitor.accept(person.getIconUri());
             }
         }
@@ -2722,7 +2745,7 @@
                     visitor.accept(message.getDataUri());
 
                     Person senderPerson = message.getSenderPerson();
-                    if (senderPerson != null && senderPerson.getIconUri() != null) {
+                    if (senderPerson != null) {
                         visitor.accept(senderPerson.getIconUri());
                     }
                 }
@@ -2735,19 +2758,15 @@
                     visitor.accept(message.getDataUri());
 
                     Person senderPerson = message.getSenderPerson();
-                    if (senderPerson != null && senderPerson.getIconUri() != null) {
+                    if (senderPerson != null) {
                         visitor.accept(senderPerson.getIconUri());
                     }
                 }
             }
         }
 
-        if (mBubbleMetadata != null && mBubbleMetadata.getIcon() != null) {
-            final Icon icon = mBubbleMetadata.getIcon();
-            final int iconType = icon.getType();
-            if (iconType == TYPE_URI_ADAPTIVE_BITMAP || iconType == TYPE_URI) {
-                visitor.accept(icon.getUri());
-            }
+        if (mBubbleMetadata != null) {
+            visitIconUri(visitor, mBubbleMetadata.getIcon());
         }
     }
 
@@ -3680,8 +3699,6 @@
         private int mTextColorsAreForBackground = COLOR_INVALID;
         private int mPrimaryTextColor = COLOR_INVALID;
         private int mSecondaryTextColor = COLOR_INVALID;
-        private int mBackgroundColor = COLOR_INVALID;
-        private int mForegroundColor = COLOR_INVALID;
         private boolean mRebuildStyledRemoteViews;
 
         private boolean mTintActionButtons;
@@ -5020,7 +5037,8 @@
         private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p,
                 TemplateBindResult result) {
             p.headerless(resId == getBaseLayoutResource()
-                    || resId == getHeadsUpBaseLayoutResource());
+                    || resId == getHeadsUpBaseLayoutResource()
+                    || resId == R.layout.notification_template_material_media);
             RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
 
             resetStandardTemplate(contentView);
@@ -5071,7 +5089,7 @@
         }
 
         private CharSequence processTextSpans(CharSequence text) {
-            if (hasForegroundColor() || mInNightMode) {
+            if (mInNightMode) {
                 return ContrastColorUtil.clearColorSpans(text);
             }
             return text;
@@ -5082,10 +5100,6 @@
             contentView.setTextColor(id, getPrimaryTextColor(p));
         }
 
-        private boolean hasForegroundColor() {
-            return mForegroundColor != COLOR_INVALID;
-        }
-
         /**
          * @param p the template params to inflate this with
          * @return the primary text color
@@ -5119,75 +5133,15 @@
                     || mSecondaryTextColor == COLOR_INVALID
                     || mTextColorsAreForBackground != backgroundColor) {
                 mTextColorsAreForBackground = backgroundColor;
-                if (!hasForegroundColor() || !isColorized(p)) {
-                    mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(mContext,
-                            backgroundColor, mInNightMode);
-                    mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(mContext,
-                            backgroundColor, mInNightMode);
-                    if (backgroundColor != COLOR_DEFAULT && isColorized(p)) {
-                        mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
-                                mPrimaryTextColor, backgroundColor, 4.5);
-                        mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
-                                mSecondaryTextColor, backgroundColor, 4.5);
-                    }
-                } else {
-                    double backLum = ContrastColorUtil.calculateLuminance(backgroundColor);
-                    double textLum = ContrastColorUtil.calculateLuminance(mForegroundColor);
-                    double contrast = ContrastColorUtil.calculateContrast(mForegroundColor,
-                            backgroundColor);
-                    // We only respect the given colors if worst case Black or White still has
-                    // contrast
-                    boolean backgroundLight = backLum > textLum
-                                    && satisfiesTextContrast(backgroundColor, Color.BLACK)
-                            || backLum <= textLum
-                                    && !satisfiesTextContrast(backgroundColor, Color.WHITE);
-                    if (contrast < 4.5f) {
-                        if (backgroundLight) {
-                            mSecondaryTextColor = ContrastColorUtil.findContrastColor(
-                                    mForegroundColor,
-                                    backgroundColor,
-                                    true /* findFG */,
-                                    4.5f);
-                            mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
-                                    mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_LIGHT);
-                        } else {
-                            mSecondaryTextColor =
-                                    ContrastColorUtil.findContrastColorAgainstDark(
-                                    mForegroundColor,
-                                    backgroundColor,
-                                    true /* findFG */,
-                                    4.5f);
-                            mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
-                                    mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_DARK);
-                        }
-                    } else {
-                        mPrimaryTextColor = mForegroundColor;
-                        mSecondaryTextColor = ContrastColorUtil.changeColorLightness(
-                                mPrimaryTextColor, backgroundLight ? LIGHTNESS_TEXT_DIFFERENCE_LIGHT
-                                        : LIGHTNESS_TEXT_DIFFERENCE_DARK);
-                        if (ContrastColorUtil.calculateContrast(mSecondaryTextColor,
-                                backgroundColor) < 4.5f) {
-                            // oh well the secondary is not good enough
-                            if (backgroundLight) {
-                                mSecondaryTextColor = ContrastColorUtil.findContrastColor(
-                                        mSecondaryTextColor,
-                                        backgroundColor,
-                                        true /* findFG */,
-                                        4.5f);
-                            } else {
-                                mSecondaryTextColor
-                                        = ContrastColorUtil.findContrastColorAgainstDark(
-                                        mSecondaryTextColor,
-                                        backgroundColor,
-                                        true /* findFG */,
-                                        4.5f);
-                            }
-                            mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
-                                    mSecondaryTextColor, backgroundLight
-                                            ? -LIGHTNESS_TEXT_DIFFERENCE_LIGHT
-                                            : -LIGHTNESS_TEXT_DIFFERENCE_DARK);
-                        }
-                    }
+                mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(mContext,
+                        backgroundColor, mInNightMode);
+                mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(mContext,
+                        backgroundColor, mInNightMode);
+                if (backgroundColor != COLOR_DEFAULT && isColorized(p)) {
+                    mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
+                            mPrimaryTextColor, backgroundColor, 4.5);
+                    mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
+                            mSecondaryTextColor, backgroundColor, 4.5);
                 }
             }
         }
@@ -5233,11 +5187,7 @@
                 result = new TemplateBindResult();
             }
             bindLargeIcon(contentView, p, result);
-            if (p.mHeaderless) {
-                // views in the headerless (collapsed) state
-                result.mHeadingExtraMarginSet.applyToView(contentView,
-                        R.id.notification_headerless_view_column);
-            } else {
+            if (!p.mHeaderless) {
                 // views in states with a header (big states)
                 result.mHeadingExtraMarginSet.applyToView(contentView, R.id.notification_header);
                 result.mTitleMarginSet.applyToView(contentView, R.id.title);
@@ -5257,6 +5207,8 @@
                 @NonNull TemplateBindResult result) {
             final Resources resources = mContext.getResources();
             final float density = resources.getDisplayMetrics().density;
+            final float iconMarginDp = resources.getDimension(
+                    R.dimen.notification_right_icon_content_margin) / density;
             final float contentMarginDp = resources.getDimension(
                     R.dimen.notification_content_margin_end) / density;
             final float expanderSizeDp = resources.getDimension(
@@ -5278,7 +5230,7 @@
                     }
                 }
             }
-            final float extraMarginEndDpIfVisible = viewWidthDp + contentMarginDp;
+            final float extraMarginEndDpIfVisible = viewWidthDp + iconMarginDp;
             result.setRightIconState(largeIconShown, viewWidthDp,
                     extraMarginEndDpIfVisible, expanderSizeDp);
         }
@@ -5342,7 +5294,7 @@
 
         private void bindHeaderChronometerAndTime(RemoteViews contentView,
                 StandardTemplateParams p, boolean hasTextToLeft) {
-            if (showsTimeOrChronometer()) {
+            if (!p.mHideTime && showsTimeOrChronometer()) {
                 if (hasTextToLeft) {
                     contentView.setViewVisibility(R.id.time_divider, View.VISIBLE);
                     setTextViewColorSecondary(contentView, R.id.time_divider, p);
@@ -5373,6 +5325,9 @@
          */
         private boolean bindHeaderText(RemoteViews contentView, StandardTemplateParams p,
                 boolean hasTextToLeft) {
+            if (p.mHideSubText) {
+                return false;
+            }
             CharSequence summaryText = p.summaryText;
             if (summaryText == null && mStyle != null && mStyle.mSummaryTextSet
                     && mStyle.hasSummaryInHeader()) {
@@ -5403,6 +5358,9 @@
          */
         private boolean bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p,
                 boolean hasTextToLeft) {
+            if (p.mHideSubText) {
+                return false;
+            }
             if (!TextUtils.isEmpty(p.headerTextSecondary)) {
                 contentView.setTextViewText(R.id.header_text_secondary, processTextSpans(
                         processLegacyText(p.headerTextSecondary)));
@@ -6633,7 +6591,7 @@
          */
         private @ColorInt int getUnresolvedBackgroundColor(StandardTemplateParams p) {
             if (isColorized(p)) {
-                return mBackgroundColor != COLOR_INVALID ? mBackgroundColor : getRawColor(p);
+                return getRawColor(p);
             } else {
                 return COLOR_DEFAULT;
             }
@@ -6661,21 +6619,6 @@
         }
 
         /**
-         * Set a color palette to be used as the background and textColors
-         *
-         * @param backgroundColor the color to be used as the background
-         * @param foregroundColor the color to be used as the foreground
-         *
-         * @hide
-         */
-        public void setColorPalette(@ColorInt int backgroundColor, @ColorInt int foregroundColor) {
-            mBackgroundColor = backgroundColor;
-            mForegroundColor = foregroundColor;
-            mTextColorsAreForBackground = COLOR_INVALID;
-            ensureColors(mParams.reset().fillTextsFrom(this));
-        }
-
-        /**
          * Forces all styled remoteViews to be built from scratch and not use any cached
          * RemoteViews.
          * This is needed for legacy apps that are baking in their remoteviews into the
@@ -6731,24 +6674,14 @@
         if (mLargeIcon != null || largeIcon != null) {
             Resources resources = context.getResources();
             Class<? extends Style> style = getNotificationStyle();
-            int maxWidth = resources.getDimensionPixelSize(isLowRam
+            int maxSize = resources.getDimensionPixelSize(isLowRam
                     ? R.dimen.notification_right_icon_size_low_ram
                     : R.dimen.notification_right_icon_size);
-            int maxHeight = maxWidth;
-            if (MediaStyle.class.equals(style)
-                    || DecoratedMediaCustomViewStyle.class.equals(style)) {
-                maxHeight = resources.getDimensionPixelSize(isLowRam
-                        ? R.dimen.notification_media_image_max_height_low_ram
-                        : R.dimen.notification_media_image_max_height);
-                maxWidth = resources.getDimensionPixelSize(isLowRam
-                        ? R.dimen.notification_media_image_max_width_low_ram
-                        : R.dimen.notification_media_image_max_width);
-            }
             if (mLargeIcon != null) {
-                mLargeIcon.scaleDownIfNecessary(maxWidth, maxHeight);
+                mLargeIcon.scaleDownIfNecessary(maxSize, maxSize);
             }
             if (largeIcon != null) {
-                largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxWidth, maxHeight);
+                largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxSize, maxSize);
             }
         }
         reduceImageSizesForRemoteView(contentView, context, isLowRam);
@@ -6835,9 +6768,6 @@
      * @hide
      */
     public boolean isColorized() {
-        if (isColorizedMedia()) {
-            return true;
-        }
         return extras.getBoolean(EXTRA_COLORIZED)
                 && (hasColorizedPermission() || isForegroundService());
     }
@@ -6851,27 +6781,6 @@
     }
 
     /**
-     * @return true if this notification is colorized and it is a media notification
-     *
-     * @hide
-     */
-    public boolean isColorizedMedia() {
-        Class<? extends Style> style = getNotificationStyle();
-        if (MediaStyle.class.equals(style)) {
-            Boolean colorized = (Boolean) extras.get(EXTRA_COLORIZED);
-            if ((colorized == null || colorized) && hasMediaSession()) {
-                return true;
-            }
-        } else if (DecoratedMediaCustomViewStyle.class.equals(style)) {
-            if (extras.getBoolean(EXTRA_COLORIZED) && hasMediaSession()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-
-    /**
      * @return true if this is a media notification
      *
      * @hide
@@ -7159,15 +7068,6 @@
 
         /**
          * @hide
-         * @return true if the style positions the progress bar on the second line; false if the
-         *         style hides the progress bar
-         */
-        protected boolean hasProgress() {
-            return true;
-        }
-
-        /**
-         * @hide
          * @return Whether we should put the summary be put into the notification header
          */
         public boolean hasSummaryInHeader() {
@@ -7231,7 +7131,7 @@
      * @see Notification#bigContentView
      */
     public static class BigPictureStyle extends Style {
-        private Bitmap mPicture;
+        private Icon mPictureIcon;
         private Icon mBigLargeIcon;
         private boolean mBigLargeIconSet = false;
         private CharSequence mPictureContentDescription;
@@ -7271,7 +7171,7 @@
          * Set the content description of the big picture.
          */
         @NonNull
-        public BigPictureStyle bigPictureContentDescription(
+        public BigPictureStyle setContentDescription(
                 @Nullable CharSequence contentDescription) {
             mPictureContentDescription = contentDescription;
             return this;
@@ -7280,8 +7180,12 @@
         /**
          * @hide
          */
-        public Bitmap getBigPicture() {
-            return mPicture;
+        @Nullable
+        public Icon getBigPicture() {
+            if (mPictureIcon != null) {
+                return mPictureIcon;
+            }
+            return null;
         }
 
         /**
@@ -7289,7 +7193,16 @@
          */
         @NonNull
         public BigPictureStyle bigPicture(@Nullable Bitmap b) {
-            mPicture = b;
+            mPictureIcon = b == null ? null : Icon.createWithBitmap(b);
+            return this;
+        }
+
+        /**
+         * Provide the content Uri to be used as the payload for the BigPicture notification.
+         */
+        @NonNull
+        public BigPictureStyle bigPicture(@Nullable Icon icon) {
+            mPictureIcon = icon;
             return this;
         }
 
@@ -7331,10 +7244,8 @@
         @Override
         public void purgeResources() {
             super.purgeResources();
-            if (mPicture != null &&
-                mPicture.isMutable() &&
-                mPicture.getAllocationByteCount() >= MIN_ASHMEM_BITMAP_SIZE) {
-                mPicture = mPicture.asShared();
+            if (mPictureIcon != null) {
+                mPictureIcon.convertToAshmem();
             }
             if (mBigLargeIcon != null) {
                 mBigLargeIcon.convertToAshmem();
@@ -7349,14 +7260,14 @@
             super.reduceImageSizes(context);
             Resources resources = context.getResources();
             boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
-            if (mPicture != null) {
+            if (mPictureIcon != null) {
                 int maxPictureWidth = resources.getDimensionPixelSize(isLowRam
                         ? R.dimen.notification_big_picture_max_height_low_ram
                         : R.dimen.notification_big_picture_max_height);
                 int maxPictureHeight = resources.getDimensionPixelSize(isLowRam
                         ? R.dimen.notification_big_picture_max_width_low_ram
                         : R.dimen.notification_big_picture_max_width);
-                mPicture = Icon.scaleDownIfNecessary(mPicture, maxPictureWidth, maxPictureHeight);
+                mPictureIcon.scaleDownIfNecessary(maxPictureWidth, maxPictureHeight);
             }
             if (mBigLargeIcon != null) {
                 int rightIconSize = resources.getDimensionPixelSize(isLowRam
@@ -7371,12 +7282,12 @@
          */
         @Override
         public RemoteViews makeContentView(boolean increasedHeight) {
-            if (mPicture == null || !mShowBigPictureWhenCollapsed) {
+            if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) {
                 return super.makeContentView(increasedHeight);
             }
 
             Icon oldLargeIcon = mBuilder.mN.mLargeIcon;
-            mBuilder.mN.mLargeIcon = Icon.createWithBitmap(mPicture);
+            mBuilder.mN.mLargeIcon = mPictureIcon;
             // The legacy largeIcon might not allow us to clear the image, as it's taken in
             // replacement if the other one is null. Because we're restoring these legacy icons
             // for old listeners, this is in general non-null.
@@ -7401,12 +7312,12 @@
          */
         @Override
         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
-            if (mPicture == null || !mShowBigPictureWhenCollapsed) {
+            if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) {
                 return super.makeHeadsUpContentView(increasedHeight);
             }
 
             Icon oldLargeIcon = mBuilder.mN.mLargeIcon;
-            mBuilder.mN.mLargeIcon = Icon.createWithBitmap(mPicture);
+            mBuilder.mN.mLargeIcon = mPictureIcon;
             // The legacy largeIcon might not allow us to clear the image, as it's taken in
             // replacement if the other one is null. Because we're restoring these legacy icons
             // for old listeners, this is in general non-null.
@@ -7463,7 +7374,7 @@
                 mBuilder.mN.largeIcon = largeIconLegacy;
             }
 
-            contentView.setImageViewBitmap(R.id.big_picture, mPicture);
+            contentView.setImageViewIcon(R.id.big_picture, mPictureIcon);
 
             if (mPictureContentDescription != null) {
                 contentView.setContentDescription(R.id.big_picture, mPictureContentDescription);
@@ -7486,7 +7397,17 @@
                         mPictureContentDescription);
             }
             extras.putBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED, mShowBigPictureWhenCollapsed);
-            extras.putParcelable(EXTRA_PICTURE, mPicture);
+
+            // If the icon contains a bitmap, use the old extra so that listeners which look for
+            // that extra can still find the picture.  Don't include the new extra in that case,
+            // to avoid duplicating data.
+            if (mPictureIcon != null && mPictureIcon.getType() == Icon.TYPE_BITMAP) {
+                extras.putParcelable(EXTRA_PICTURE, mPictureIcon.getBitmap());
+                extras.putParcelable(EXTRA_PICTURE_ICON, null);
+            } else {
+                extras.putParcelable(EXTRA_PICTURE, null);
+                extras.putParcelable(EXTRA_PICTURE_ICON, mPictureIcon);
+            }
         }
 
         /**
@@ -7507,7 +7428,16 @@
             }
 
             mShowBigPictureWhenCollapsed = extras.getBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED);
-            mPicture = extras.getParcelable(EXTRA_PICTURE);
+
+            // When this style adds a picture, we only add one of the keys.  If both were added,
+            // it would most likely be a legacy app trying to override the picture in some way.
+            // Because of that case it's better to give precedence to the legacy field.
+            Bitmap bitmapPicture = extras.getParcelable(EXTRA_PICTURE);
+            if (bitmapPicture != null) {
+                mPictureIcon = Icon.createWithBitmap(bitmapPicture);
+            } else {
+                mPictureIcon = extras.getParcelable(EXTRA_PICTURE_ICON);
+            }
         }
 
         /**
@@ -7529,20 +7459,32 @@
                 return true;
             }
             BigPictureStyle otherS = (BigPictureStyle) other;
-            return areBitmapsObviouslyDifferent(getBigPicture(), otherS.getBigPicture());
+            return areIconsObviouslyDifferent(getBigPicture(), otherS.getBigPicture());
         }
 
-        private static boolean areBitmapsObviouslyDifferent(Bitmap a, Bitmap b) {
+        private static boolean areIconsObviouslyDifferent(Icon a, Icon b) {
             if (a == b) {
                 return false;
             }
             if (a == null || b == null) {
                 return true;
             }
-            return a.getWidth() != b.getWidth()
-                    || a.getHeight() != b.getHeight()
-                    || a.getConfig() != b.getConfig()
-                    || a.getGenerationId() != b.getGenerationId();
+            if (a.sameAs(b)) {
+                return false;
+            }
+            final int aType = a.getType();
+            if (aType != b.getType()) {
+                return true;
+            }
+            if (aType == Icon.TYPE_BITMAP || aType == Icon.TYPE_ADAPTIVE_BITMAP) {
+                final Bitmap aBitmap = a.getBitmap();
+                final Bitmap bBitmap = b.getBitmap();
+                return aBitmap.getWidth() != bBitmap.getWidth()
+                        || aBitmap.getHeight() != bBitmap.getHeight()
+                        || aBitmap.getConfig() != bBitmap.getConfig()
+                        || aBitmap.getGenerationId() != bBitmap.getGenerationId();
+            }
+            return true;
         }
     }
 
@@ -8978,7 +8920,7 @@
          */
         @Override
         public RemoteViews makeContentView(boolean increasedHeight) {
-            return makeMediaContentView();
+            return makeMediaContentView(null /* customContent */);
         }
 
         /**
@@ -8986,7 +8928,7 @@
          */
         @Override
         public RemoteViews makeBigContentView() {
-            return makeMediaBigContentView();
+            return makeMediaBigContentView(null /* customContent */);
         }
 
         /**
@@ -8994,7 +8936,7 @@
          */
         @Override
         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
-            return makeMediaContentView();
+            return makeMediaContentView(null /* customContent */);
         }
 
         /** @hide */
@@ -9064,88 +9006,72 @@
             container.setContentDescription(buttonId, action.title);
         }
 
-        private RemoteViews makeMediaContentView() {
-            StandardTemplateParams p = mBuilder.mParams.reset()
-                    .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
-                    .hideProgress(true)
-                    .fillTextsFrom(mBuilder);
-            RemoteViews view = mBuilder.applyStandardTemplate(
-                    R.layout.notification_template_material_media, p,
-                    null /* result */);
-
+        /** @hide */
+        protected RemoteViews makeMediaContentView(@Nullable RemoteViews customContent) {
             final int numActions = mBuilder.mActions.size();
-            final int numActionsToShow = mActionsToShowInCompact == null
-                    ? 0
-                    : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
+            final int numActionsToShow = Math.min(mActionsToShowInCompact == null
+                    ? 0 : mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
             if (numActionsToShow > numActions) {
                 throw new IllegalArgumentException(String.format(
                         "setShowActionsInCompactView: action %d out of bounds (max %d)",
                         numActions, numActions - 1));
             }
+
+            StandardTemplateParams p = mBuilder.mParams.reset()
+                    .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
+                    .hideTime(numActionsToShow > 1)    // hide if actions wider than a large icon
+                    .hideSubText(numActionsToShow > 1) // hide if actions wider than a large icon
+                    .hideLargeIcon(numActionsToShow > 0)  // large icon or actions; not both
+                    .hideProgress(true)
+                    .fillTextsFrom(mBuilder);
+            TemplateBindResult result = new TemplateBindResult();
+            RemoteViews template = mBuilder.applyStandardTemplate(
+                    R.layout.notification_template_material_media, p,
+                    null /* result */);
+
             for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) {
                 if (i < numActionsToShow) {
                     final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
-                    bindMediaActionButton(view, MEDIA_BUTTON_IDS[i], action, p);
+                    bindMediaActionButton(template, MEDIA_BUTTON_IDS[i], action, p);
                 } else {
-                    view.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
+                    template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
                 }
             }
-            handleImage(view);
-            // handle the content margin
-            int endMargin = R.dimen.notification_content_margin_end;
-            if (mBuilder.mN.hasLargeIcon()) {
-                endMargin = R.dimen.notification_media_image_margin_end;
-            }
-            view.setViewLayoutMarginDimen(R.id.notification_main_column,
-                            RemoteViews.MARGIN_END, endMargin);
-            return view;
+            // Prevent a swooping expand animation when there are no actions
+            boolean hasActions = numActionsToShow != 0;
+            template.setViewVisibility(R.id.media_actions, hasActions ? View.VISIBLE : View.GONE);
+
+            // Add custom view if provided by subclass.
+            buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result,
+                    DevFlags.DECORATION_PARTIAL);
+            return template;
         }
 
-        private RemoteViews makeMediaBigContentView() {
+        /** @hide */
+        protected RemoteViews makeMediaBigContentView(@Nullable RemoteViews customContent) {
             final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
-            // Dont add an expanded view if there is no more content to be revealed
-            int actionsInCompact = mActionsToShowInCompact == null
-                    ? 0
-                    : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
-            if (!mBuilder.mN.hasLargeIcon() && actionCount <= actionsInCompact) {
-                return null;
-            }
             StandardTemplateParams p = mBuilder.mParams.reset()
                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
                     .hideProgress(true)
                     .fillTextsFrom(mBuilder);
-            RemoteViews big = mBuilder.applyStandardTemplate(
-                    R.layout.notification_template_material_big_media, p , null /* result */);
+            TemplateBindResult result = new TemplateBindResult();
+            RemoteViews template = mBuilder.applyStandardTemplate(
+                    R.layout.notification_template_material_big_media, p , result);
 
             for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) {
                 if (i < actionCount) {
-                    bindMediaActionButton(big, MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p);
+                    bindMediaActionButton(template,
+                            MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p);
                 } else {
-                    big.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
+                    template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
                 }
             }
-            handleImage(big);
-            return big;
-        }
-
-        private void handleImage(RemoteViews contentView) {
-            if (mBuilder.mN.hasLargeIcon()) {
-                contentView.setViewLayoutMarginDimen(R.id.title, RemoteViews.MARGIN_END, 0);
-                contentView.setViewLayoutMarginDimen(R.id.text, RemoteViews.MARGIN_END, 0);
-            }
-        }
-
-        /**
-         * @hide
-         */
-        @Override
-        protected boolean hasProgress() {
-            return false;
+            buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result,
+                    DevFlags.DECORATION_PARTIAL);
+            return template;
         }
     }
 
-
-
     /**
      * Helper class for generating large-format notifications that include a large image attachment.
      *
@@ -9833,9 +9759,7 @@
          */
         @Override
         public RemoteViews makeContentView(boolean increasedHeight) {
-            RemoteViews remoteViews = super.makeContentView(false /* increasedHeight */);
-            return buildIntoRemoteView(remoteViews, R.id.notification_content_container,
-                    mBuilder.mN.contentView);
+            return makeMediaContentView(mBuilder.mN.contentView);
         }
 
         /**
@@ -9843,24 +9767,10 @@
          */
         @Override
         public RemoteViews makeBigContentView() {
-            RemoteViews customRemoteView = mBuilder.mN.bigContentView != null
+            RemoteViews customContent = mBuilder.mN.bigContentView != null
                     ? mBuilder.mN.bigContentView
                     : mBuilder.mN.contentView;
-            return makeBigContentViewWithCustomContent(customRemoteView);
-        }
-
-        private RemoteViews makeBigContentViewWithCustomContent(RemoteViews customRemoteView) {
-            RemoteViews remoteViews = super.makeBigContentView();
-            if (remoteViews != null) {
-                return buildIntoRemoteView(remoteViews, R.id.notification_main_column,
-                        customRemoteView);
-            } else if (customRemoteView != mBuilder.mN.contentView){
-                remoteViews = super.makeContentView(false /* increasedHeight */);
-                return buildIntoRemoteView(remoteViews, R.id.notification_content_container,
-                        customRemoteView);
-            } else {
-                return null;
-            }
+            return makeMediaBigContentView(customContent);
         }
 
         /**
@@ -9868,10 +9778,10 @@
          */
         @Override
         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
-            RemoteViews customRemoteView = mBuilder.mN.headsUpContentView != null
+            RemoteViews customContent = mBuilder.mN.headsUpContentView != null
                     ? mBuilder.mN.headsUpContentView
                     : mBuilder.mN.contentView;
-            return makeBigContentViewWithCustomContent(customRemoteView);
+            return makeMediaBigContentView(customContent);
         }
 
         /**
@@ -9886,18 +9796,21 @@
             return false;
         }
 
-        private RemoteViews buildIntoRemoteView(RemoteViews remoteViews, int id,
-                RemoteViews customContent) {
+        private RemoteViews buildIntoRemoteView(RemoteViews template, RemoteViews customContent,
+                boolean headerless) {
             if (customContent != null) {
                 // Need to clone customContent before adding, because otherwise it can no longer be
                 // parceled independently of remoteViews.
                 customContent = customContent.clone();
                 customContent.overrideTextColors(mBuilder.getPrimaryTextColor(mBuilder.mParams));
-                remoteViews.removeAllViews(id);
-                remoteViews.addView(id, customContent);
-                remoteViews.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
+                if (headerless) {
+                    template.removeFromParent(R.id.notification_top_line);
+                }
+                template.removeAllViews(R.id.notification_main_column);
+                template.addView(R.id.notification_main_column, customContent);
+                template.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
             }
-            return remoteViews;
+            return template;
         }
     }
 
@@ -12190,6 +12103,8 @@
         boolean mHeaderless;
         boolean mHideAppName;
         boolean mHideTitle;
+        boolean mHideSubText;
+        boolean mHideTime;
         boolean mHideActions;
         boolean mHideProgress;
         boolean mHideSnoozeButton;
@@ -12212,6 +12127,8 @@
             mHeaderless = false;
             mHideAppName = false;
             mHideTitle = false;
+            mHideSubText = false;
+            mHideTime = false;
             mHideActions = false;
             mHideProgress = false;
             mHideSnoozeButton = false;
@@ -12225,6 +12142,7 @@
             summaryText = null;
             headerTextSecondary = null;
             maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
+            hideLargeIcon = false;
             allowColorization = true;
             mReduceHighlights = false;
             return this;
@@ -12249,6 +12167,16 @@
             return this;
         }
 
+        public StandardTemplateParams hideSubText(boolean hideSubText) {
+            mHideSubText = hideSubText;
+            return this;
+        }
+
+        public StandardTemplateParams hideTime(boolean hideTime) {
+            mHideTime = hideTime;
+            return this;
+        }
+
         final StandardTemplateParams hideActions(boolean hideActions) {
             this.mHideActions = hideActions;
             return this;
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index 6b2e649..d640a6f 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -189,7 +189,7 @@
         } else {
             palette = Palette
                     .from(bitmap, new CelebiQuantizer())
-                    .maximumColorCount(256)
+                    .maximumColorCount(5)
                     .resizeBitmapArea(MAX_WALLPAPER_EXTRACTION_AREA)
                     .generate();
         }
diff --git a/core/java/android/app/WindowContext.java b/core/java/android/app/WindowContext.java
index cbe2995..d44918c 100644
--- a/core/java/android/app/WindowContext.java
+++ b/core/java/android/app/WindowContext.java
@@ -20,8 +20,11 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UiContext;
+import android.content.ComponentCallbacks;
+import android.content.ComponentCallbacksController;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -49,6 +52,8 @@
     private final IWindowManager mWms;
     private final WindowTokenClient mToken;
     private boolean mListenerRegistered;
+    private final ComponentCallbacksController mCallbacksController =
+            new ComponentCallbacksController();
 
     /**
      * Default constructor. Will generate a {@link WindowTokenClient} and attach this context to
@@ -131,8 +136,24 @@
     }
 
     void destroy() {
+        mCallbacksController.clearCallbacks();
         final ContextImpl impl = (ContextImpl) getBaseContext();
         impl.scheduleFinalCleanup(getClass().getName(), "WindowContext");
         Reference.reachabilityFence(this);
     }
+
+    @Override
+    public void registerComponentCallbacks(@NonNull ComponentCallbacks callback) {
+        mCallbacksController.registerCallbacks(callback);
+    }
+
+    @Override
+    public void unregisterComponentCallbacks(@NonNull ComponentCallbacks callback) {
+        mCallbacksController.unregisterCallbacks(callback);
+    }
+
+    /** Dispatch {@link Configuration} to each {@link ComponentCallbacks}. */
+    void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
+        mCallbacksController.dispatchConfigurationChanged(newConfig);
+    }
 }
diff --git a/core/java/android/app/WindowTokenClient.java b/core/java/android/app/WindowTokenClient.java
index 2298e84..82cef07 100644
--- a/core/java/android/app/WindowTokenClient.java
+++ b/core/java/android/app/WindowTokenClient.java
@@ -61,7 +61,7 @@
 
     @Override
     public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
-        final Context context = mContextRef.get();
+        final WindowContext context = mContextRef.get();
         if (context == null) {
             return;
         }
@@ -72,6 +72,8 @@
         if (displayChanged || configChanged) {
             // TODO(ag/9789103): update resource manager logic to track non-activity tokens
             mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId);
+            ActivityThread.currentActivityThread().getHandler().post(
+                    () -> context.dispatchConfigurationChanged(newConfig));
         }
         if (displayChanged) {
             context.updateDisplay(newDisplayId);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 426159f..117df02 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9990,26 +9990,24 @@
     }
 
     /**
-     * Sets whether 5g slicing is enabled on the work profile.
+     * Sets whether enterprise network preference is enabled on the work profile.
      *
-     * Slicing allows operators to virtually divide their networks in portions and use different
-     * portions for specific use cases; for example, a corporation can have a deal/agreement with
-     * a carrier that all of its employees’ devices use data on a slice dedicated for enterprise
-     * use.
+     * For example, a corporation can have a deal/agreement with a carrier that all of its
+     * employees’ devices use data on a network preference dedicated for enterprise use.
      *
-     * By default, 5g slicing is enabled on the work profile on supported carriers and devices.
-     * Admins can explicitly disable it with this API.
+     * By default, enterprise network preference is enabled on the work profile on supported
+     * carriers and devices. Admins can explicitly disable it with this API.
      *
      * <p>This method can only be called by the profile owner of a managed profile.
      *
-     * @param enabled whether 5g Slice should be enabled.
+     * @param enabled whether enterprise network preference should be enabled.
      * @throws SecurityException if the caller is not the profile owner.
      **/
-    public void setNetworkSlicingEnabled(boolean enabled) {
-        throwIfParentInstance("setNetworkSlicingEnabled");
+    public void setEnterpriseNetworkPreferenceEnabled(boolean enabled) {
+        throwIfParentInstance("setEnterpriseNetworkPreferenceEnabled");
         if (mService != null) {
             try {
-                mService.setNetworkSlicingEnabled(enabled);
+                mService.setEnterpriseNetworkPreferenceEnabled(enabled);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -10017,20 +10015,20 @@
     }
 
     /**
-     * Indicates whether 5g slicing is enabled.
+     * Indicates whether whether enterprise network preference is enabled.
      *
      * <p>This method can be called by the profile owner of a managed profile.
      *
-     * @return whether 5g Slice is enabled.
+     * @return whether whether enterprise network preference is enabled.
      * @throws SecurityException if the caller is not the profile owner.
      */
-    public boolean isNetworkSlicingEnabled() {
-        throwIfParentInstance("isNetworkSlicingEnabled");
+    public boolean isEnterpriseNetworkPreferenceEnabled() {
+        throwIfParentInstance("isEnterpriseNetworkPreferenceEnabled");
         if (mService == null) {
             return false;
         }
         try {
-            return mService.isNetworkSlicingEnabled(myUserId());
+            return mService.isEnterpriseNetworkPreferenceEnabled(myUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 7901791..0ad92b7 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -268,8 +268,8 @@
     void setSecondaryLockscreenEnabled(in ComponentName who, boolean enabled);
     boolean isSecondaryLockscreenEnabled(in UserHandle userHandle);
 
-    void setNetworkSlicingEnabled(in boolean enabled);
-    boolean isNetworkSlicingEnabled(int userHandle);
+    void setEnterpriseNetworkPreferenceEnabled(in boolean enabled);
+    boolean isEnterpriseNetworkPreferenceEnabled(int userHandle);
 
     void setLockTaskPackages(in ComponentName who, in String[] packages);
     String[] getLockTaskPackages(in ComponentName who);
diff --git a/core/java/android/app/backup/RestoreSet.java b/core/java/android/app/backup/RestoreSet.java
index 6759346..51430c0 100644
--- a/core/java/android/app/backup/RestoreSet.java
+++ b/core/java/android/app/backup/RestoreSet.java
@@ -16,6 +16,7 @@
 
 package android.app.backup;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -32,12 +33,14 @@
      * Name of this restore set.  May be user generated, may simply be the name
      * of the handset model, e.g. "T-Mobile G1".
      */
+    @Nullable
     public String name;
 
     /**
      * Identifier of the device whose data this is.  This will be as unique as
      * is practically possible; for example, it might be an IMEI.
      */
+    @Nullable
     public String device;
 
     /**
@@ -47,15 +50,48 @@
      */
     public long token;
 
+    /**
+     * Properties of the {@link BackupTransport} transport that was used to obtain the data in
+     * this restore set.
+     */
+    public final int backupTransportFlags;
 
+    /**
+     * Constructs a RestoreSet object that identifies a set of data that can be restored.
+     */
     public RestoreSet() {
         // Leave everything zero / null
+        backupTransportFlags = 0;
     }
 
-    public RestoreSet(String _name, String _dev, long _token) {
-        name = _name;
-        device = _dev;
-        token = _token;
+    /**
+     * Constructs a RestoreSet object that identifies a set of data that can be restored.
+     *
+     * @param name The name of the restore set.
+     * @param device The name of the device where the restore data is coming from.
+     * @param token The unique identifier for the current restore set.
+     */
+    public RestoreSet(@Nullable String name, @Nullable String device, long token) {
+        this(name, device, token, /* backupTransportFlags */ 0);
+    }
+
+    /**
+     * Constructs a RestoreSet object that identifies a set of data that can be restored.
+     *
+     * @param name The name of the restore set.
+     * @param device The name of the device where the restore data is coming from.
+     * @param token The unique identifier for the current restore set.
+     * @param backupTransportFlags Flags returned by {@link BackupTransport#getTransportFlags()}
+     *                             implementation of the backup transport used by the source device
+     *                             to create this restore set. See {@link BackupAgent} for possible
+     *                             flag values.
+     */
+    public RestoreSet(@Nullable String name, @Nullable String device, long token,
+            int backupTransportFlags) {
+        this.name = name;
+        this.device = device;
+        this.token = token;
+        this.backupTransportFlags = backupTransportFlags;
     }
 
     // Parcelable implementation
@@ -67,6 +103,7 @@
         out.writeString(name);
         out.writeString(device);
         out.writeLong(token);
+        out.writeInt(backupTransportFlags);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<RestoreSet> CREATOR
@@ -84,5 +121,6 @@
         name = in.readString();
         device = in.readString();
         token = in.readLong();
+        backupTransportFlags = in.readInt();
     }
 }
diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java
index 5d50c5d7..ef92172 100644
--- a/core/java/android/app/usage/UsageStats.java
+++ b/core/java/android/app/usage/UsageStats.java
@@ -110,7 +110,9 @@
     public long mTotalTimeForegroundServiceUsed;
 
     /**
-     * Last time this package's component is used, measured in milliseconds since the epoch.
+     * Last time this package's component is used by a client package, measured in milliseconds
+     * since the epoch. Note that component usage is only reported in certain cases (e.g. broadcast
+     * receiver, service, content provider).
      * See {@link UsageEvents.Event#APP_COMPONENT_USED}
      * @hide
      */
@@ -274,8 +276,10 @@
     }
 
     /**
-     * Get the last time this package's component was used, measured in milliseconds since the
-     * epoch.
+     * Get the last time this package's component was used by a client package, measured in
+     * milliseconds since the epoch. Note that component usage is only reported in certain cases
+     * (e.g. broadcast receiver, service, content provider).
+     * See {@link UsageEvents.Event#APP_COMPONENT_USED}
      * @hide
      */
     @SystemApi
diff --git a/core/java/android/apphibernation/AppHibernationManager.java b/core/java/android/apphibernation/AppHibernationManager.java
index 132cc40..de77848 100644
--- a/core/java/android/apphibernation/AppHibernationManager.java
+++ b/core/java/android/apphibernation/AppHibernationManager.java
@@ -24,6 +24,8 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 
+import java.util.List;
+
 /**
  * This class provides an API surface for system apps to manipulate the app hibernation
  * state of a package for the user provided in the context.
@@ -111,4 +113,20 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Get the hibernating packages for the user. This is equivalent to the list of packages for
+     * the user that return true for {@link #isHibernatingForUser}.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(value = android.Manifest.permission.MANAGE_APP_HIBERNATION)
+    public @NonNull List<String> getHibernatingPackagesForUser() {
+        try {
+            return mIAppHibernationService.getHibernatingPackagesForUser(mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/apphibernation/IAppHibernationService.aidl b/core/java/android/apphibernation/IAppHibernationService.aidl
index 6a068ee..afdb3fe 100644
--- a/core/java/android/apphibernation/IAppHibernationService.aidl
+++ b/core/java/android/apphibernation/IAppHibernationService.aidl
@@ -25,4 +25,5 @@
     void setHibernatingForUser(String packageName, int userId, boolean isHibernating);
     boolean isHibernatingGlobally(String packageName);
     void setHibernatingGlobally(String packageName, boolean isHibernating);
+    List<String> getHibernatingPackagesForUser(int userId);
 }
\ No newline at end of file
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index a6b4b47..82da4fb 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -841,8 +841,8 @@
      * Calling this method will trigger a full re-inflation of the App Widget.
      *
      * The color resources that can be overloaded are the ones whose name is prefixed with
-     * {@code system_primary_}, {@code system_secondary_} or {@code system_neutral_}, for example
-     * {@link android.R.color#system_primary_500}.
+     * {@code system_neutral} or {@code system_accent}, for example
+     * {@link android.R.color#system_neutral1_500}.
      */
     public void setColorResources(@NonNull SparseIntArray colorMapping) {
         mColorResources = RemoteViews.ColorResources.create(mContext, colorMapping);
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 8d41572..a3d19ca 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -52,6 +52,8 @@
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.internal.util.Preconditions;
+
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -3152,6 +3154,25 @@
         return true;
     }
 
+    /**
+     * Determines whether a String Bluetooth address, such as "00:43:A8:23:10:F0"
+     * is a RANDOM STATIC address.
+     *
+     * RANDOM STATIC: (addr & 0b11) == 0b11
+     * RANDOM RESOLVABLE: (addr & 0b11) == 0b10
+     * RANDOM non-RESOLVABLE: (addr & 0b11) == 0b00
+     *
+     * @param address Bluetooth address as string
+     * @return true if the 2 Least Significant Bits of the address equals 0b11.
+     *
+     * @hide
+     */
+    public static boolean isAddressRandomStatic(@NonNull String address) {
+        Preconditions.checkNotNull(address);
+        return checkBluetoothAddress(address)
+                && (Integer.parseInt(address.split(":")[5], 16) & 0b11) == 0b11;
+    }
+
     @UnsupportedAppUsage
     /*package*/ IBluetoothManager getBluetoothManager() {
         return mManagerService;
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 07dbdce..8908649 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -1022,6 +1022,24 @@
     public static final String EXTRA_MAS_INSTANCE =
             "android.bluetooth.device.extra.MAS_INSTANCE";
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+        prefix = { "ADDRESS_TYPE_" },
+        value = {
+            /** Hardware MAC Address */
+            ADDRESS_TYPE_PUBLIC,
+            /** Address is either resolvable, non-resolvable or static.*/
+            ADDRESS_TYPE_RANDOM,
+        }
+    )
+    public @interface AddressType {}
+
+    /** Hardware MAC Address of the device */
+    public static final int ADDRESS_TYPE_PUBLIC = 0;
+    /** Address is either resolvable, non-resolvable or static. */
+    public static final int ADDRESS_TYPE_RANDOM = 1;
+
     /**
      * Lazy initialization. Guaranteed final after first object constructed, or
      * getService() called.
@@ -1030,6 +1048,7 @@
     private static volatile IBluetooth sService;
 
     private final String mAddress;
+    @AddressType private final int mAddressType;
 
     /*package*/
     @UnsupportedAppUsage
@@ -1084,6 +1103,7 @@
         }
 
         mAddress = address;
+        mAddressType = ADDRESS_TYPE_PUBLIC;
     }
 
     @Override
diff --git a/core/java/android/bluetooth/le/ScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java
index 51f63f7..7459f87 100644
--- a/core/java/android/bluetooth/le/ScanFilter.java
+++ b/core/java/android/bluetooth/le/ScanFilter.java
@@ -16,15 +16,19 @@
 
 package android.bluetooth.le;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothDevice.AddressType;
 import android.os.Parcel;
 import android.os.ParcelUuid;
 import android.os.Parcelable;
 
 import com.android.internal.util.BitUtils;
+import com.android.internal.util.Preconditions;
 
 import java.util.Arrays;
 import java.util.List;
@@ -53,6 +57,11 @@
     @Nullable
     private final String mDeviceAddress;
 
+    private final @AddressType int mAddressType;
+
+    @Nullable
+    private final byte[] mIrk;
+
     @Nullable
     private final ParcelUuid mServiceUuid;
     @Nullable
@@ -79,12 +88,12 @@
     /** @hide */
     public static final ScanFilter EMPTY = new ScanFilter.Builder().build();
 
-
     private ScanFilter(String name, String deviceAddress, ParcelUuid uuid,
             ParcelUuid uuidMask, ParcelUuid solicitationUuid,
             ParcelUuid solicitationUuidMask, ParcelUuid serviceDataUuid,
             byte[] serviceData, byte[] serviceDataMask,
-            int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask) {
+            int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask,
+            @AddressType int addressType, @Nullable byte[] irk) {
         mDeviceName = name;
         mServiceUuid = uuid;
         mServiceUuidMask = uuidMask;
@@ -97,6 +106,8 @@
         mManufacturerId = manufacturerId;
         mManufacturerData = manufacturerData;
         mManufacturerDataMask = manufacturerDataMask;
+        mAddressType = addressType;
+        mIrk = irk;
     }
 
     @Override
@@ -280,6 +291,23 @@
         return mDeviceAddress;
     }
 
+    /**
+     * @hide
+     */
+    @SystemApi
+    public @AddressType int getAddressType() {
+        return mAddressType;
+    }
+
+    /**
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    public byte[] getIrk() {
+        return mIrk;
+    }
+
     @Nullable
     public byte[] getServiceData() {
         return mServiceData;
@@ -516,8 +544,16 @@
      */
     public static final class Builder {
 
+        /**
+         * @hide
+         */
+        @SystemApi
+        public static final int LEN_IRK_OCTETS = 16;
+
         private String mDeviceName;
         private String mDeviceAddress;
+        private @AddressType int mAddressType = BluetoothDevice.ADDRESS_TYPE_PUBLIC;
+        private byte[] mIrk;
 
         private ParcelUuid mServiceUuid;
         private ParcelUuid mUuidMask;
@@ -546,14 +582,130 @@
          *
          * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
          * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
-         * BluetoothAdapter#checkBluetoothAddress}.
+         * BluetoothAdapter#checkBluetoothAddress}.  The @AddressType is defaulted to {@link
+         * BluetoothDevice#ADDRESS_TYPE_PUBLIC}
          * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
          */
         public Builder setDeviceAddress(String deviceAddress) {
-            if (deviceAddress != null && !BluetoothAdapter.checkBluetoothAddress(deviceAddress)) {
+            return setDeviceAddress(mDeviceAddress, BluetoothDevice.ADDRESS_TYPE_PUBLIC);
+        }
+
+        /**
+         * Set filter on Address with AddressType
+         *
+         * <p>This key is used to resolve a private address from a public address.
+         *
+         * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
+         * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
+         * BluetoothAdapter#checkBluetoothAddress}. May be any type of address.
+         * @param addressType indication of the type of address
+         * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC}
+         * or {@link BluetoothDevice#ADDRESS_TYPE_RANDOM}
+         *
+         * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
+         * @throws IllegalArgumentException If the {@code addressType} is invalid length
+         * @throws NullPointerException if {@code deviceAddress} is null.
+         *
+         * @hide
+         */
+        @NonNull
+        @SystemApi
+        public Builder setDeviceAddress(@NonNull String deviceAddress,
+                                        @AddressType int addressType) {
+            return setDeviceAddressInternal(deviceAddress, addressType, null);
+        }
+
+        /**
+         * Set filter on Address with AddressType and the Identity Resolving Key (IRK).
+         *
+         * <p>The IRK is used to resolve a {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC} from
+         * a PRIVATE_ADDRESS type.
+         *
+         * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
+         * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
+         * BluetoothAdapter#checkBluetoothAddress}.  This Address type must only be PUBLIC OR RANDOM
+         * STATIC.
+         * @param addressType indication of the type of address
+         * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC}
+         * or {@link BluetoothDevice#ADDRESS_TYPE_RANDOM}
+         * @param irk non-null byte array representing the Identity Resolving Key
+         *
+         * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
+         * @throws IllegalArgumentException if the {@code irk} is invalid length.
+         * @throws IllegalArgumentException If the {@code addressType} is invalid length or is not
+         * PUBLIC or RANDOM STATIC when an IRK is present.
+         * @throws NullPointerException if {@code deviceAddress} or {@code irk} is null.
+         *
+         * @hide
+         */
+        @NonNull
+        @SystemApi
+        public Builder setDeviceAddress(@NonNull String deviceAddress,
+                                        @AddressType int addressType,
+                                        @NonNull byte[] irk) {
+            Preconditions.checkNotNull(irk);
+            if (irk.length != LEN_IRK_OCTETS) {
+                throw new IllegalArgumentException("'irk' is invalid length!");
+            }
+            return setDeviceAddressInternal(deviceAddress, addressType, irk);
+        }
+
+        /**
+         * Set filter on Address with AddressType and the Identity Resolving Key (IRK).
+         *
+         * <p>Internal setter for the device address
+         *
+         * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
+         * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
+         * BluetoothAdapter#checkBluetoothAddress}.
+         * @param addressType indication of the type of address
+         * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC}
+         * @param irk non-null byte array representing the Identity Resolving Address; nullable
+         * internally.
+         *
+         * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
+         * @throws IllegalArgumentException If the {@code addressType} is invalid length.
+         * @throws NullPointerException if {@code deviceAddress} is null.
+         *
+         * @hide
+         */
+        @NonNull
+        private Builder setDeviceAddressInternal(@NonNull String deviceAddress,
+                                                 @AddressType int addressType,
+                                                 @Nullable byte[] irk) {
+
+            // Make sure our deviceAddress is valid!
+            Preconditions.checkNotNull(deviceAddress);
+            if (!BluetoothAdapter.checkBluetoothAddress(deviceAddress)) {
                 throw new IllegalArgumentException("invalid device address " + deviceAddress);
             }
+
+            // Verify type range
+            if (addressType < BluetoothDevice.ADDRESS_TYPE_PUBLIC
+                || addressType > BluetoothDevice.ADDRESS_TYPE_RANDOM) {
+                throw new IllegalArgumentException("'addressType' is invalid!");
+            }
+
+            // IRK can only be used for a PUBLIC or RANDOM (STATIC) Address.
+            if (addressType == BluetoothDevice.ADDRESS_TYPE_RANDOM) {
+                // Don't want a bad combination of address and irk!
+                if (irk != null) {
+                    // Since there are 3 possible RANDOM subtypes we must check to make sure
+                    // the correct type of address is used.
+                    if (!BluetoothAdapter.isAddressRandomStatic(deviceAddress)) {
+                        throw new IllegalArgumentException(
+                                "Invalid combination: IRK requires either a PUBLIC or "
+                                + "RANDOM (STATIC) Address");
+                    }
+                }
+            }
+
+            // PUBLIC doesn't require extra work
+            // Without an IRK any address may be accepted
+
             mDeviceAddress = deviceAddress;
+            mAddressType = addressType;
+            mIrk = irk;
             return this;
         }
 
@@ -727,7 +879,8 @@
                     mServiceUuid, mUuidMask, mServiceSolicitationUuid,
                     mServiceSolicitationUuidMask,
                     mServiceDataUuid, mServiceData, mServiceDataMask,
-                    mManufacturerId, mManufacturerData, mManufacturerDataMask);
+                    mManufacturerId, mManufacturerData, mManufacturerDataMask,
+                    mAddressType, mIrk);
         }
     }
 }
diff --git a/core/java/android/bluetooth/le/ScanSettings.java b/core/java/android/bluetooth/le/ScanSettings.java
index 504118e..368d1ee 100644
--- a/core/java/android/bluetooth/le/ScanSettings.java
+++ b/core/java/android/bluetooth/le/ScanSettings.java
@@ -52,6 +52,16 @@
     public static final int SCAN_MODE_LOW_LATENCY = 2;
 
     /**
+     * Perform Bluetooth LE scan in ambient discovery mode. This mode has lower duty cycle and more
+     * aggressive scan interval than balanced mode that provides a good trade-off between scan
+     * latency and power consumption.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int SCAN_MODE_AMBIENT_DISCOVERY = 3;
+
+    /**
      * Trigger a callback for every Bluetooth advertisement found that matches the filter criteria.
      * If no filter is active, all advertisement packets are reported.
      */
@@ -276,10 +286,17 @@
          * @throws IllegalArgumentException If the {@code scanMode} is invalid.
          */
         public Builder setScanMode(int scanMode) {
-            if (scanMode < SCAN_MODE_OPPORTUNISTIC || scanMode > SCAN_MODE_LOW_LATENCY) {
-                throw new IllegalArgumentException("invalid scan mode " + scanMode);
+            switch (scanMode) {
+                case SCAN_MODE_OPPORTUNISTIC:
+                case SCAN_MODE_LOW_POWER:
+                case SCAN_MODE_BALANCED:
+                case SCAN_MODE_LOW_LATENCY:
+                case SCAN_MODE_AMBIENT_DISCOVERY:
+                    mScanMode = scanMode;
+                    break;
+                default:
+                    throw new IllegalArgumentException("invalid scan mode " + scanMode);
             }
-            mScanMode = scanMode;
             return this;
         }
 
diff --git a/core/java/android/content/ComponentCallbacksController.java b/core/java/android/content/ComponentCallbacksController.java
new file mode 100644
index 0000000..a81aaf7
--- /dev/null
+++ b/core/java/android/content/ComponentCallbacksController.java
@@ -0,0 +1,127 @@
+/*
+ * 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.content;
+
+import android.annotation.NonNull;
+import android.content.res.Configuration;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A helper class to manage {@link ComponentCallbacks} and {@link ComponentCallbacks2}, such as
+ * registering ,unregistering {@link ComponentCallbacks} and sending callbacks to all registered
+ * {@link ComponentCallbacks}.
+ *
+ * @see Context#registerComponentCallbacks(ComponentCallbacks)
+ * @see Context#unregisterComponentCallbacks(ComponentCallbacks)
+ * @see ComponentCallbacks
+ * @see ComponentCallbacks2
+ *
+ * @hide
+ */
+public class ComponentCallbacksController {
+    @GuardedBy("mLock")
+    private List<ComponentCallbacks> mComponentCallbacks;
+
+    private final Object mLock = new Object();
+
+    /**
+     * Register the {@link ComponentCallbacks}.
+     *
+     * @see Context#registerComponentCallbacks(ComponentCallbacks)
+     */
+    public void registerCallbacks(@NonNull ComponentCallbacks callbacks) {
+        synchronized (mLock) {
+            if (mComponentCallbacks == null) {
+                mComponentCallbacks = new ArrayList<>();
+            }
+            mComponentCallbacks.add(callbacks);
+        }
+    }
+
+    /**
+     * Unregister the {@link ComponentCallbacks}.
+     *
+     * @see Context#unregisterComponentCallbacks(ComponentCallbacks)
+     */
+    public void unregisterCallbacks(@NonNull ComponentCallbacks callbacks) {
+        synchronized (mLock) {
+            if (mComponentCallbacks == null || mComponentCallbacks.isEmpty()) {
+                return;
+            }
+            mComponentCallbacks.remove(callbacks);
+        }
+    }
+
+    /**
+     * Clear all registered {@link ComponentCallbacks}.
+     * It is useful when the associated {@link Context} is going to be released.
+     */
+    public void clearCallbacks() {
+        synchronized (mLock) {
+            if (mComponentCallbacks != null) {
+                mComponentCallbacks.clear();
+            }
+        }
+    }
+
+    /**
+     * Sending {@link ComponentCallbacks#onConfigurationChanged(Configuration)} to all registered
+     * {@link ComponentCallbacks}.
+     */
+    public void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
+        forAllComponentCallbacks(callbacks -> callbacks.onConfigurationChanged(newConfig));
+    }
+
+    /**
+     * Sending {@link ComponentCallbacks#onLowMemory()} to all registered
+     * {@link ComponentCallbacks}.
+     */
+    public void dispatchLowMemory() {
+        forAllComponentCallbacks(ComponentCallbacks::onLowMemory);
+    }
+
+    /**
+     * Sending {@link ComponentCallbacks2#onTrimMemory(int)} to all registered
+     * {@link ComponentCallbacks2}.
+     */
+    public void dispatchTrimMemory(int level) {
+        forAllComponentCallbacks(callbacks -> {
+            if (callbacks instanceof ComponentCallbacks2) {
+                ((ComponentCallbacks2) callbacks).onTrimMemory(level);
+            }
+        });
+    }
+
+    private void forAllComponentCallbacks(Consumer<ComponentCallbacks> callbacksConsumer) {
+        final ComponentCallbacks[] callbacksArray;
+        synchronized (mLock) {
+            if (mComponentCallbacks == null || mComponentCallbacks.isEmpty()) {
+                return;
+            }
+            callbacksArray = new ComponentCallbacks[mComponentCallbacks.size()];
+            mComponentCallbacks.toArray(callbacksArray);
+        }
+        for (ComponentCallbacks callbacks : callbacksArray) {
+            callbacksConsumer.accept(callbacks);
+        }
+    }
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 64ca92f..92ff640 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -655,12 +655,21 @@
     /**
      * Add a new {@link ComponentCallbacks} to the base application of the
      * Context, which will be called at the same times as the ComponentCallbacks
-     * methods of activities and other components are called.  Note that you
+     * methods of activities and other components are called. Note that you
      * <em>must</em> be sure to use {@link #unregisterComponentCallbacks} when
      * appropriate in the future; this will not be removed for you.
+     * <p>
+     * After {@link Build.VERSION_CODES#S}, Registering the ComponentCallbacks to Context created
+     * via {@link #createWindowContext(int, Bundle)} or
+     * {@link #createWindowContext(Display, int, Bundle)} will receive
+     * {@link ComponentCallbacks#onConfigurationChanged(Configuration)} from Window Context rather
+     * than its base application. It is helpful if you want to handle UI components that
+     * associated with the Window Context when the Window Context has configuration changes.</p>
      *
      * @param callback The interface to call.  This can be either a
      * {@link ComponentCallbacks} or {@link ComponentCallbacks2} interface.
+     *
+     * @see Context#createWindowContext(int, Bundle)
      */
     public void registerComponentCallbacks(ComponentCallbacks callback) {
         getApplicationContext().registerComponentCallbacks(callback);
@@ -6358,6 +6367,16 @@
      * windowContext.getSystemService(WindowManager.class).addView(overlayView, mParams);
      * </pre>
      * <p>
+     * After {@link Build.VERSION_CODES#S}, window context provides the capability to listen to its
+     * {@link Configuration} changes by calling
+     * {@link #registerComponentCallbacks(ComponentCallbacks)}, while other kinds of {@link Context}
+     * will register the {@link ComponentCallbacks} to {@link #getApplicationContext() its
+     * Application context}. Note that window context only propagate
+     * {@link ComponentCallbacks#onConfigurationChanged(Configuration)} callback.
+     * {@link ComponentCallbacks#onLowMemory()} or other callbacks in {@link ComponentCallbacks2}
+     * won't be invoked.
+     * </p>
+     * <p>
      * Note that using {@link android.app.Application} or {@link android.app.Service} context for
      * UI-related queries may result in layout or continuity issues on devices with variable screen
      * sizes (e.g. foldables) or in multi-window modes, since these non-UI contexts may not reflect
diff --git a/core/java/android/content/TEST_MAPPING b/core/java/android/content/TEST_MAPPING
index a2880df..614143e 100644
--- a/core/java/android/content/TEST_MAPPING
+++ b/core/java/android/content/TEST_MAPPING
@@ -44,9 +44,12 @@
         },
         {
           "include-filter": "android.content.ContextTest"
+        },
+        {
+          "include-filter": "android.content.ComponentCallbacksControllerTest"
         }
       ],
-      "file_patterns": ["(/|^)Context.java", "(/|^)ContextWrapper.java"]
+      "file_patterns": ["(/|^)Context.java", "(/|^)ContextWrapper.java", "(/|^)ComponentCallbacksController.java"]
     }
   ]
 }
\ No newline at end of file
diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java
index 5af3b5a..c04d3be 100644
--- a/core/java/android/content/pm/AppSearchShortcutInfo.java
+++ b/core/java/android/content/pm/AppSearchShortcutInfo.java
@@ -40,6 +40,7 @@
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -327,6 +328,19 @@
         return si;
     }
 
+    /**
+     * @hide
+     */
+    @NonNull
+    public static List<GenericDocument> toGenericDocuments(
+            @NonNull final Collection<ShortcutInfo> shortcuts) {
+        final List<GenericDocument> docs = new ArrayList<>(shortcuts.size());
+        for (ShortcutInfo si : shortcuts) {
+            docs.add(AppSearchShortcutInfo.instance(si));
+        }
+        return docs;
+    }
+
     /** @hide */
     @VisibleForTesting
     public static class Builder extends GenericDocument.Builder<Builder> {
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index cfb6e1b..5a89708 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -321,6 +321,10 @@
         return UserManager.isUserTypeManagedProfile(userType);
     }
 
+    public boolean isCloneProfile() {
+        return UserManager.isUserTypeCloneProfile(userType);
+    }
+
     @UnsupportedAppUsage
     public boolean isEnabled() {
         return (flags & FLAG_DISABLED) != FLAG_DISABLED;
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
index 55e19f2..d187f60 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -341,11 +341,11 @@
     }
 
     /**
-     * Retrieve the user selection data for the given {@param packageName} and the current user.
+     * Retrieve the user state for the given package and the {@link Context}'s user.
      *
      * @param packageName The app to query state for.
-     * @return the user selection verification data for the given package for the current user, or
-     * null if the package does not declare any HTTP/HTTPS domains.
+     * @return The user selection verification data for the given package for the user, or null if
+     * the package does not declare any HTTP/HTTPS domains.
      */
     @Nullable
     public DomainVerificationUserState getDomainVerificationUserState(
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 3da1227..96e7d3b 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -326,6 +326,20 @@
      * @hide
      */
     public static final int ALLOWED_METERED_REASON_USER_EXEMPTED = 1 << 16;
+    /**
+     * Flag to indicate that app is exempt from certain metered network restrictions because of it
+     * being a system component.
+     *
+     * @hide
+     */
+    public static final int ALLOWED_METERED_REASON_SYSTEM = 1 << 17;
+    /**
+     * Flag to indicate that app is exempt from certain metered network restrictions because of it
+     * being in the foreground.
+     *
+     * @hide
+     */
+    public static final int ALLOWED_METERED_REASON_FOREGROUND = 1 << 18;
 
     /** @hide */
     public static final int ALLOWED_METERED_REASON_MASK = 0xffff0000;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index fa6472e..4674aa2 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -6030,7 +6030,7 @@
                     pw.print(":");
                     for (int it=0; it<types.size(); it++) {
                         pw.print(" ");
-                        pw.print(JobParameters.getReasonCodeDescription(types.keyAt(it)));
+                        pw.print(JobParameters.getLegacyReasonCodeDescription(types.keyAt(it)));
                         pw.print("(");
                         pw.print(types.valueAt(it));
                         pw.print("x)");
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 087568d..34f2c103f 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -99,6 +99,8 @@
     boolean someUserHasSeedAccount(in String accountName, in String accountType);
     boolean isProfile(int userId);
     boolean isManagedProfile(int userId);
+    boolean isCloneProfile(int userId);
+    boolean sharesMediaWithParent(int userId);
     boolean isDemoUser(int userId);
     boolean isPreCreated(int userId);
     UserInfo createProfileForUserEvenWhenDisallowedWithThrow(in String name, in String userType, int flags,
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index b42a495..219912c 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -218,7 +218,7 @@
 
     @Override
     public boolean[] arePrimitivesSupported(
-            @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
+            @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) {
         boolean[] supported = new boolean[primitiveIds.length];
         if (mVibratorManager == null) {
             Log.w(TAG, "Failed to check supported primitives; no vibrator manager.");
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
index b528eb1..841aad5 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -211,7 +211,7 @@
 
         @Override
         public boolean[] arePrimitivesSupported(
-                @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
+                @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) {
             boolean[] supported = new boolean[primitiveIds.length];
             for (int i = 0; i < primitiveIds.length; i++) {
                 supported[i] = mVibratorInfo.isPrimitiveSupported(primitiveIds[i]);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 5069e031..d4de4fa 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -25,6 +25,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.StringDef;
+import android.annotation.SuppressAutoDoc;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -136,6 +137,16 @@
     public static final String USER_TYPE_PROFILE_MANAGED = "android.os.usertype.profile.MANAGED";
 
     /**
+     * User type representing a clone profile. Clone profile is a user profile type used to run
+     * second instance of an otherwise single user App (eg, messengers). Only the primary user
+     * is allowed to have a clone profile.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String USER_TYPE_PROFILE_CLONE = "android.os.usertype.profile.CLONE";
+
+    /**
      * User type representing a generic profile for testing purposes. Only on debuggable builds.
      * @hide
      */
@@ -1984,6 +1995,14 @@
     }
 
     /**
+     * Returns whether the user type is a {@link UserManager#USER_TYPE_PROFILE_CLONE clone user}.
+     * @hide
+     */
+    public static boolean isUserTypeCloneProfile(String userType) {
+        return USER_TYPE_PROFILE_CLONE.equals(userType);
+    }
+
+    /**
      * Returns the enum defined in the statsd UserLifecycleJourneyReported atom corresponding to the
      * user type.
      * @hide
@@ -2233,6 +2252,31 @@
     }
 
     /**
+     * Checks if the context user is a clone profile.
+     *
+     * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} or
+     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the caller
+     * must be in the same profile group of the user.
+     *
+     * @return whether the context user is a clone profile.
+     *
+     * @see android.os.UserManager#USER_TYPE_PROFILE_CLONE
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+            Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+    @UserHandleAware
+    @SuppressAutoDoc
+    public boolean isCloneProfile() {
+        try {
+            return mService.isCloneProfile(mUserId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Checks if the calling app is running as an ephemeral user.
      *
      * @return whether the caller is an ephemeral user.
@@ -4064,6 +4108,31 @@
     }
 
     /**
+     * If the user is a {@link UserManager#isProfile profile}, checks if the user
+     * shares media with its parent user (the user that created this profile).
+     * Returns false for any other type of user.
+     *
+     * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} or
+     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the
+     * caller must be in the same profile group as the user.
+     *
+     * @return true if the user shares media with its parent user, false otherwise.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+            Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+    @UserHandleAware
+    @SuppressAutoDoc
+    public boolean sharesMediaWithParent() {
+        try {
+            return mService.sharesMediaWithParent(mUserId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Removes a user and all associated data.
      * @param userId the integer handle of the user.
      * @hide
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 217f178..cec323f 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -21,6 +21,8 @@
 import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.media.AudioAttributes;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.VibrationEffectSegment;
 import android.util.Slog;
 
 import java.lang.annotation.Retention;
@@ -330,9 +332,9 @@
 
         private void applyHapticFeedbackHeuristics(@Nullable VibrationEffect effect) {
             if (effect != null) {
-                if (mUsage == USAGE_UNKNOWN && effect instanceof VibrationEffect.Prebaked) {
-                    VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
-                    switch (prebaked.getId()) {
+                PrebakedSegment prebaked = extractPrebakedSegment(effect);
+                if (mUsage == USAGE_UNKNOWN && prebaked != null) {
+                    switch (prebaked.getEffectId()) {
                         case VibrationEffect.EFFECT_CLICK:
                         case VibrationEffect.EFFECT_DOUBLE_CLICK:
                         case VibrationEffect.EFFECT_HEAVY_CLICK:
@@ -355,6 +357,20 @@
             }
         }
 
+        @Nullable
+        private PrebakedSegment extractPrebakedSegment(VibrationEffect effect) {
+            if (effect instanceof VibrationEffect.Composed) {
+                VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+                if (composed.getSegments().size() == 1) {
+                    VibrationEffectSegment segment = composed.getSegments().get(0);
+                    if (segment instanceof PrebakedSegment) {
+                        return (PrebakedSegment) segment;
+                    }
+                }
+            }
+            return null;
+        }
+
         private void setUsage(@NonNull AudioAttributes audio) {
             mOriginalAudioUsage = audio.getUsage();
             switch (audio.getUsage()) {
diff --git a/core/java/android/os/VibrationEffect.aidl b/core/java/android/os/VibrationEffect.aidl
index 89478fa..6311760 100644
--- a/core/java/android/os/VibrationEffect.aidl
+++ b/core/java/android/os/VibrationEffect.aidl
@@ -17,4 +17,3 @@
 package android.os;
 
 parcelable VibrationEffect;
-parcelable VibrationEffect.Composition.PrimitiveEffect;
\ No newline at end of file
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 0199fad..95b5e85 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -28,6 +28,10 @@
 import android.hardware.vibrator.V1_0.EffectStrength;
 import android.hardware.vibrator.V1_3.Effect;
 import android.net.Uri;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
 import android.util.MathUtils;
 
 import com.android.internal.util.Preconditions;
@@ -45,11 +49,6 @@
  * These effects may be any number of things, from single shot vibrations to complex waveforms.
  */
 public abstract class VibrationEffect implements Parcelable {
-    private static final int PARCEL_TOKEN_ONE_SHOT = 1;
-    private static final int PARCEL_TOKEN_WAVEFORM = 2;
-    private static final int PARCEL_TOKEN_EFFECT = 3;
-    private static final int PARCEL_TOKEN_COMPOSITION = 4;
-
     // Stevens' coefficient to scale the perceived vibration intensity.
     private static final float SCALE_GAMMA = 0.65f;
 
@@ -181,9 +180,7 @@
      * @return The desired effect.
      */
     public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
-        VibrationEffect effect = new OneShot(milliseconds, amplitude);
-        effect.validate();
-        return effect;
+        return createWaveform(new long[]{milliseconds}, new int[]{amplitude}, -1 /* repeat */);
     }
 
     /**
@@ -243,7 +240,19 @@
      * @return The desired effect.
      */
     public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
-        VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
+        if (timings.length != amplitudes.length) {
+            throw new IllegalArgumentException(
+                    "timing and amplitude arrays must be of equal length"
+                            + " (timings.length=" + timings.length
+                            + ", amplitudes.length=" + amplitudes.length + ")");
+        }
+        List<StepSegment> segments = new ArrayList<>();
+        for (int i = 0; i < timings.length; i++) {
+            float parsedAmplitude = amplitudes[i] == DEFAULT_AMPLITUDE
+                    ? DEFAULT_AMPLITUDE : (float) amplitudes[i] / MAX_AMPLITUDE;
+            segments.add(new StepSegment(parsedAmplitude, (int) timings[i]));
+        }
+        VibrationEffect effect = new Composed(segments, repeat);
         effect.validate();
         return effect;
     }
@@ -317,7 +326,8 @@
      */
     @TestApi
     public static VibrationEffect get(int effectId, boolean fallback) {
-        VibrationEffect effect = new Prebaked(effectId, fallback, EffectStrength.MEDIUM);
+        VibrationEffect effect = new Composed(
+                new PrebakedSegment(effectId, fallback, EffectStrength.MEDIUM));
         effect.validate();
         return effect;
     }
@@ -428,32 +438,28 @@
     public abstract <T extends VibrationEffect> T scale(float scaleFactor);
 
     /**
-     * Scale given vibration intensity by the given factor.
+     * Applies given effect strength to prebaked effects represented by one of
+     * VibrationEffect.EFFECT_*.
      *
-     * @param amplitude amplitude of the effect, must be between 0 and MAX_AMPLITUDE
-     * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
-     *                    scale down the intensity, values larger than 1 will scale up
-     *
+     * @param effectStrength new effect strength to be applied, one of
+     *                       VibrationEffect.EFFECT_STRENGTH_*.
+     * @return this if there is no change to this effect, or a copy of this effect with applied
+     * effect strength otherwise.
      * @hide
      */
-    protected static int scale(int amplitude, float scaleFactor) {
-        if (amplitude == 0) {
-            return 0;
-        }
-        int scaled = (int) (scale((float) amplitude / MAX_AMPLITUDE, scaleFactor) * MAX_AMPLITUDE);
-        return MathUtils.constrain(scaled, 1, MAX_AMPLITUDE);
+    public <T extends VibrationEffect> T applyEffectStrength(int effectStrength) {
+        return (T) this;
     }
 
     /**
      * Scale given vibration intensity by the given factor.
      *
-     * @param intensity relative intensity of the effect, must be between 0 and 1
+     * @param intensity   relative intensity of the effect, must be between 0 and 1
      * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
      *                    scale down the intensity, values larger than 1 will scale up
-     *
      * @hide
      */
-    protected static float scale(float intensity, float scaleFactor) {
+    public static float scale(float intensity, float scaleFactor) {
         // Applying gamma correction to the scale factor, which is the same as encoding the input
         // value, scaling it, then decoding the scaled value.
         float scale = MathUtils.pow(scaleFactor, 1f / SCALE_GAMMA);
@@ -516,545 +522,152 @@
         }
     }
 
-    /** @hide */
+    /**
+     * Implementation of {@link VibrationEffect} described by a composition of one or more
+     * {@link VibrationEffectSegment}, with an optional index to represent repeating effects.
+     *
+     * @hide
+     */
     @TestApi
-    public static class OneShot extends VibrationEffect implements Parcelable {
-        private final long mDuration;
-        private final int mAmplitude;
+    public static final class Composed extends VibrationEffect {
+        private final ArrayList<VibrationEffectSegment> mSegments;
+        private final int mRepeatIndex;
 
-        public OneShot(Parcel in) {
-            mDuration = in.readLong();
-            mAmplitude = in.readInt();
+        Composed(@NonNull Parcel in) {
+            this(in.readArrayList(VibrationEffectSegment.class.getClassLoader()), in.readInt());
         }
 
-        public OneShot(long milliseconds, int amplitude) {
-            mDuration = milliseconds;
-            mAmplitude = amplitude;
-        }
-
-        @Override
-        public long getDuration() {
-            return mDuration;
-        }
-
-        public int getAmplitude() {
-            return mAmplitude;
+        Composed(@NonNull VibrationEffectSegment segment) {
+            this(Arrays.asList(segment), /* repeatIndex= */ -1);
         }
 
         /** @hide */
-        @Override
-        public OneShot scale(float scaleFactor) {
-            if (scaleFactor == 1f || mAmplitude == DEFAULT_AMPLITUDE) {
-                // Just return this if there's no scaling to be done or if amplitude is not yet set.
-                return this;
-            }
-            return new OneShot(mDuration, scale(mAmplitude, scaleFactor));
+        public Composed(@NonNull List<? extends VibrationEffectSegment> segments, int repeatIndex) {
+            super();
+            mSegments = new ArrayList<>(segments);
+            mRepeatIndex = repeatIndex;
         }
 
-        /** @hide */
-        @Override
-        public OneShot resolve(int defaultAmplitude) {
-            if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude <= 0) {
-                throw new IllegalArgumentException(
-                        "amplitude must be between 1 and 255 inclusive (amplitude="
-                        + defaultAmplitude + ")");
-            }
-            if (mAmplitude == DEFAULT_AMPLITUDE) {
-                return new OneShot(mDuration, defaultAmplitude);
-            }
-            return this;
-        }
-
-        /** @hide */
-        @Override
-        public void validate() {
-            if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
-                throw new IllegalArgumentException(
-                        "amplitude must either be DEFAULT_AMPLITUDE, "
-                        + "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
-            }
-            if (mDuration <= 0) {
-                throw new IllegalArgumentException(
-                        "duration must be positive (duration=" + mDuration + ")");
-            }
-        }
-
-        @Override
-        public boolean equals(@Nullable Object o) {
-            if (!(o instanceof VibrationEffect.OneShot)) {
-                return false;
-            }
-            VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
-            return other.mDuration == mDuration && other.mAmplitude == mAmplitude;
-        }
-
-        @Override
-        public int hashCode() {
-            int result = 17;
-            result += 37 * (int) mDuration;
-            result += 37 * mAmplitude;
-            return result;
-        }
-
-        @Override
-        public String toString() {
-            return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}";
-        }
-
-        @Override
-        public void writeToParcel(Parcel out, int flags) {
-            out.writeInt(PARCEL_TOKEN_ONE_SHOT);
-            out.writeLong(mDuration);
-            out.writeInt(mAmplitude);
-        }
-
-        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-        public static final @android.annotation.NonNull Parcelable.Creator<OneShot> CREATOR =
-            new Parcelable.Creator<OneShot>() {
-                @Override
-                public OneShot createFromParcel(Parcel in) {
-                    // Skip the type token
-                    in.readInt();
-                    return new OneShot(in);
-                }
-                @Override
-                public OneShot[] newArray(int size) {
-                    return new OneShot[size];
-                }
-            };
-    }
-
-    /** @hide */
-    @TestApi
-    public static class Waveform extends VibrationEffect implements Parcelable {
-        private final long[] mTimings;
-        private final int[] mAmplitudes;
-        private final int mRepeat;
-
-        public Waveform(Parcel in) {
-            this(in.createLongArray(), in.createIntArray(), in.readInt());
-        }
-
-        public Waveform(long[] timings, int[] amplitudes, int repeat) {
-            mTimings = new long[timings.length];
-            System.arraycopy(timings, 0, mTimings, 0, timings.length);
-            mAmplitudes = new int[amplitudes.length];
-            System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
-            mRepeat = repeat;
-        }
-
-        public long[] getTimings() {
-            return mTimings;
-        }
-
-        public int[] getAmplitudes() {
-            return mAmplitudes;
+        @NonNull
+        public List<VibrationEffectSegment> getSegments() {
+            return mSegments;
         }
 
         public int getRepeatIndex() {
-            return mRepeat;
+            return mRepeatIndex;
+        }
+
+        @Override
+        public void validate() {
+            int segmentCount = mSegments.size();
+            boolean hasNonZeroDuration = false;
+            boolean hasNonZeroAmplitude = false;
+            for (int i = 0; i < segmentCount; i++) {
+                VibrationEffectSegment segment = mSegments.get(i);
+                segment.validate();
+                // A segment with unknown duration = -1 still counts as a non-zero duration.
+                hasNonZeroDuration |= segment.getDuration() != 0;
+                hasNonZeroAmplitude |= segment.hasNonZeroAmplitude();
+            }
+            if (!hasNonZeroDuration) {
+                throw new IllegalArgumentException("at least one timing must be non-zero"
+                        + " (segments=" + mSegments + ")");
+            }
+            if (!hasNonZeroAmplitude) {
+                throw new IllegalArgumentException("at least one amplitude must be non-zero"
+                        + " (segments=" + mSegments + ")");
+            }
+            if (mRepeatIndex != -1) {
+                Preconditions.checkArgumentInRange(mRepeatIndex, 0, segmentCount - 1,
+                        "repeat index must be within the bounds of the segments (segments.length="
+                                + segmentCount + ", index=" + mRepeatIndex + ")");
+            }
         }
 
         @Override
         public long getDuration() {
-            if (mRepeat >= 0) {
+            if (mRepeatIndex >= 0) {
                 return Long.MAX_VALUE;
             }
-            long duration = 0;
-            for (long d : mTimings) {
-                duration += d;
-            }
-            return duration;
-        }
-
-        /** @hide */
-        @Override
-        public Waveform scale(float scaleFactor) {
-            if (scaleFactor == 1f) {
-                // Just return this if there's no scaling to be done.
-                return this;
-            }
-            boolean scaled = false;
-            int[] scaledAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
-            for (int i = 0; i < scaledAmplitudes.length; i++) {
-                if (scaledAmplitudes[i] == DEFAULT_AMPLITUDE) {
-                    // Skip amplitudes that are not set.
-                    continue;
+            int segmentCount = mSegments.size();
+            long totalDuration = 0;
+            for (int i = 0; i < segmentCount; i++) {
+                long segmentDuration = mSegments.get(i).getDuration();
+                if (segmentDuration < 0) {
+                    return segmentDuration;
                 }
-                scaled = true;
-                scaledAmplitudes[i] = scale(scaledAmplitudes[i], scaleFactor);
+                totalDuration += segmentDuration;
             }
-            if (!scaled) {
-                // Just return this if no scaling was done.
-                return this;
-            }
-            return new Waveform(mTimings, scaledAmplitudes, mRepeat);
+            return totalDuration;
         }
 
-        /** @hide */
-        @Override
-        public Waveform resolve(int defaultAmplitude) {
-            if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) {
-                throw new IllegalArgumentException(
-                        "Amplitude is negative or greater than MAX_AMPLITUDE");
-            }
-            boolean resolved = false;
-            int[] resolvedAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
-            for (int i = 0; i < resolvedAmplitudes.length; i++) {
-                if (resolvedAmplitudes[i] == DEFAULT_AMPLITUDE) {
-                    resolvedAmplitudes[i] = defaultAmplitude;
-                    resolved = true;
-                }
-            }
-            if (!resolved) {
-                return this;
-            }
-            return new Waveform(mTimings, resolvedAmplitudes, mRepeat);
-        }
-
-        /** @hide */
-        @Override
-        public void validate() {
-            if (mTimings.length != mAmplitudes.length) {
-                throw new IllegalArgumentException(
-                        "timing and amplitude arrays must be of equal length"
-                        + " (timings.length=" + mTimings.length
-                        + ", amplitudes.length=" + mAmplitudes.length + ")");
-            }
-            if (!hasNonZeroEntry(mTimings)) {
-                throw new IllegalArgumentException("at least one timing must be non-zero"
-                        + " (timings=" + Arrays.toString(mTimings) + ")");
-            }
-            for (long timing : mTimings) {
-                if (timing < 0) {
-                    throw new IllegalArgumentException("timings must all be >= 0"
-                            + " (timings=" + Arrays.toString(mTimings) + ")");
-                }
-            }
-            for (int amplitude : mAmplitudes) {
-                if (amplitude < -1 || amplitude > 255) {
-                    throw new IllegalArgumentException(
-                            "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255"
-                            + " (amplitudes=" + Arrays.toString(mAmplitudes) + ")");
-                }
-            }
-            if (mRepeat < -1 || mRepeat >= mTimings.length) {
-                throw new IllegalArgumentException(
-                        "repeat index must be within the bounds of the timings array"
-                        + " (timings.length=" + mTimings.length + ", index=" + mRepeat + ")");
-            }
-        }
-
-        @Override
-        public boolean equals(@Nullable Object o) {
-            if (!(o instanceof VibrationEffect.Waveform)) {
-                return false;
-            }
-            VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
-            return Arrays.equals(mTimings, other.mTimings)
-                && Arrays.equals(mAmplitudes, other.mAmplitudes)
-                && mRepeat == other.mRepeat;
-        }
-
-        @Override
-        public int hashCode() {
-            int result = 17;
-            result += 37 * Arrays.hashCode(mTimings);
-            result += 37 * Arrays.hashCode(mAmplitudes);
-            result += 37 * mRepeat;
-            return result;
-        }
-
-        @Override
-        public String toString() {
-            return "Waveform{mTimings=" + Arrays.toString(mTimings)
-                + ", mAmplitudes=" + Arrays.toString(mAmplitudes)
-                + ", mRepeat=" + mRepeat
-                + "}";
-        }
-
-        @Override
-        public void writeToParcel(Parcel out, int flags) {
-            out.writeInt(PARCEL_TOKEN_WAVEFORM);
-            out.writeLongArray(mTimings);
-            out.writeIntArray(mAmplitudes);
-            out.writeInt(mRepeat);
-        }
-
-        private static boolean hasNonZeroEntry(long[] vals) {
-            for (long val : vals) {
-                if (val != 0) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-
-        public static final @android.annotation.NonNull Parcelable.Creator<Waveform> CREATOR =
-            new Parcelable.Creator<Waveform>() {
-                @Override
-                public Waveform createFromParcel(Parcel in) {
-                    // Skip the type token
-                    in.readInt();
-                    return new Waveform(in);
-                }
-                @Override
-                public Waveform[] newArray(int size) {
-                    return new Waveform[size];
-                }
-            };
-    }
-
-    /** @hide */
-    @TestApi
-    public static class Prebaked extends VibrationEffect implements Parcelable {
-        private final int mEffectId;
-        private final boolean mFallback;
-        private final int mEffectStrength;
-        @Nullable
-        private final VibrationEffect mFallbackEffect;
-
-        public Prebaked(Parcel in) {
-            mEffectId = in.readInt();
-            mFallback = in.readByte() != 0;
-            mEffectStrength = in.readInt();
-            mFallbackEffect = in.readParcelable(VibrationEffect.class.getClassLoader());
-        }
-
-        public Prebaked(int effectId, boolean fallback, int effectStrength) {
-            mEffectId = effectId;
-            mFallback = fallback;
-            mEffectStrength = effectStrength;
-            mFallbackEffect = null;
-        }
-
-        /** @hide */
-        public Prebaked(int effectId, int effectStrength, @NonNull VibrationEffect fallbackEffect) {
-            mEffectId = effectId;
-            mFallback = true;
-            mEffectStrength = effectStrength;
-            mFallbackEffect = fallbackEffect;
-        }
-
-        public int getId() {
-            return mEffectId;
-        }
-
-        /**
-         * Whether the effect should fall back to a generic pattern if there's no hardware specific
-         * implementation of it.
-         */
-        public boolean shouldFallback() {
-            return mFallback;
-        }
-
-        @Override
-        public long getDuration() {
-            return -1;
-        }
-
-        /** @hide */
-        @Override
-        public Prebaked resolve(int defaultAmplitude) {
-            if (mFallbackEffect != null) {
-                VibrationEffect resolvedFallback = mFallbackEffect.resolve(defaultAmplitude);
-                if (!mFallbackEffect.equals(resolvedFallback)) {
-                    return new Prebaked(mEffectId, mEffectStrength, resolvedFallback);
-                }
-            }
-            return this;
-        }
-
-        /** @hide */
-        @Override
-        public Prebaked scale(float scaleFactor) {
-            if (mFallbackEffect != null) {
-                VibrationEffect scaledFallback = mFallbackEffect.scale(scaleFactor);
-                if (!mFallbackEffect.equals(scaledFallback)) {
-                    return new Prebaked(mEffectId, mEffectStrength, scaledFallback);
-                }
-            }
-            // Prebaked effect strength cannot be scaled with this method.
-            return this;
-        }
-
-        /**
-         * Set the effect strength.
-         */
-        public int getEffectStrength() {
-            return mEffectStrength;
-        }
-
-        /**
-         * Return the fallback effect, if set.
-         *
-         * @hide
-         */
-        @Nullable
-        public VibrationEffect getFallbackEffect() {
-            return mFallbackEffect;
-        }
-
-        private static boolean isValidEffectStrength(int strength) {
-            switch (strength) {
-                case EffectStrength.LIGHT:
-                case EffectStrength.MEDIUM:
-                case EffectStrength.STRONG:
-                    return true;
-                default:
-                    return false;
-            }
-        }
-
-        /** @hide */
-        @Override
-        public void validate() {
-            switch (mEffectId) {
-                case EFFECT_CLICK:
-                case EFFECT_DOUBLE_CLICK:
-                case EFFECT_TICK:
-                case EFFECT_TEXTURE_TICK:
-                case EFFECT_THUD:
-                case EFFECT_POP:
-                case EFFECT_HEAVY_CLICK:
-                    break;
-                default:
-                    if (mEffectId < RINGTONES[0] || mEffectId > RINGTONES[RINGTONES.length - 1]) {
-                        throw new IllegalArgumentException(
-                                "Unknown prebaked effect type (value=" + mEffectId + ")");
-                    }
-            }
-            if (!isValidEffectStrength(mEffectStrength)) {
-                throw new IllegalArgumentException(
-                        "Unknown prebaked effect strength (value=" + mEffectStrength + ")");
-            }
-        }
-
-        @Override
-        public boolean equals(@Nullable Object o) {
-            if (!(o instanceof VibrationEffect.Prebaked)) {
-                return false;
-            }
-            VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
-            return mEffectId == other.mEffectId
-                && mFallback == other.mFallback
-                && mEffectStrength == other.mEffectStrength
-                && Objects.equals(mFallbackEffect, other.mFallbackEffect);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(mEffectId, mFallback, mEffectStrength, mFallbackEffect);
-        }
-
-        @Override
-        public String toString() {
-            return "Prebaked{mEffectId=" + effectIdToString(mEffectId)
-                + ", mEffectStrength=" + effectStrengthToString(mEffectStrength)
-                + ", mFallback=" + mFallback
-                + ", mFallbackEffect=" + mFallbackEffect
-                + "}";
-        }
-
-
-        @Override
-        public void writeToParcel(Parcel out, int flags) {
-            out.writeInt(PARCEL_TOKEN_EFFECT);
-            out.writeInt(mEffectId);
-            out.writeByte((byte) (mFallback ? 1 : 0));
-            out.writeInt(mEffectStrength);
-            out.writeParcelable(mFallbackEffect, flags);
-        }
-
-        public static final @NonNull Parcelable.Creator<Prebaked> CREATOR =
-            new Parcelable.Creator<Prebaked>() {
-                @Override
-                public Prebaked createFromParcel(Parcel in) {
-                    // Skip the type token
-                    in.readInt();
-                    return new Prebaked(in);
-                }
-                @Override
-                public Prebaked[] newArray(int size) {
-                    return new Prebaked[size];
-                }
-            };
-    }
-
-    /** @hide */
-    public static final class Composed extends VibrationEffect implements Parcelable {
-        private final ArrayList<Composition.PrimitiveEffect> mPrimitiveEffects;
-
-        /**
-         * @hide
-         */
-        @SuppressWarnings("unchecked")
-        public Composed(@NonNull Parcel in) {
-            this(in.readArrayList(Composed.class.getClassLoader()));
-        }
-
-        /**
-         * @hide
-         */
-        public Composed(List<Composition.PrimitiveEffect> effects) {
-            mPrimitiveEffects = new ArrayList<>(Objects.requireNonNull(effects));
-        }
-
-        /**
-         * @hide
-         */
         @NonNull
-        public List<Composition.PrimitiveEffect> getPrimitiveEffects() {
-            return mPrimitiveEffects;
-        }
-
         @Override
-        public long getDuration() {
-            return -1;
+        public Composed resolve(int defaultAmplitude) {
+            int segmentCount = mSegments.size();
+            ArrayList<VibrationEffectSegment> resolvedSegments = new ArrayList<>(segmentCount);
+            for (int i = 0; i < segmentCount; i++) {
+                resolvedSegments.add(mSegments.get(i).resolve(defaultAmplitude));
+            }
+            if (resolvedSegments.equals(mSegments)) {
+                return this;
+            }
+            Composed resolved = new Composed(resolvedSegments, mRepeatIndex);
+            resolved.validate();
+            return resolved;
         }
 
-        /** @hide */
-        @Override
-        public VibrationEffect resolve(int defaultAmplitude) {
-            // Primitive effects already have default primitive intensity set, so ignore this.
-            return this;
-        }
-
-        /** @hide */
+        @NonNull
         @Override
         public Composed scale(float scaleFactor) {
-            if (scaleFactor == 1f) {
-                // Just return this if there's no scaling to be done.
+            int segmentCount = mSegments.size();
+            ArrayList<VibrationEffectSegment> scaledSegments = new ArrayList<>(segmentCount);
+            for (int i = 0; i < segmentCount; i++) {
+                scaledSegments.add(mSegments.get(i).scale(scaleFactor));
+            }
+            if (scaledSegments.equals(mSegments)) {
                 return this;
             }
-            final int primitiveCount = mPrimitiveEffects.size();
-            List<Composition.PrimitiveEffect> scaledPrimitives = new ArrayList<>();
-            for (int i = 0; i < primitiveCount; i++) {
-                Composition.PrimitiveEffect primitive = mPrimitiveEffects.get(i);
-                scaledPrimitives.add(new Composition.PrimitiveEffect(
-                        primitive.id, scale(primitive.scale, scaleFactor), primitive.delay));
-            }
-            return new Composed(scaledPrimitives);
+            Composed scaled = new Composed(scaledSegments, mRepeatIndex);
+            scaled.validate();
+            return scaled;
         }
 
-        /** @hide */
+        @NonNull
         @Override
-        public void validate() {
-            final int primitiveCount = mPrimitiveEffects.size();
-            for (int i = 0; i < primitiveCount; i++) {
-                Composition.PrimitiveEffect primitive = mPrimitiveEffects.get(i);
-                Composition.checkPrimitive(primitive.id);
-                Preconditions.checkArgumentInRange(primitive.scale, 0.0f, 1.0f, "scale");
-                Preconditions.checkArgumentNonNegative(primitive.delay,
-                        "Primitive delay must be zero or positive");
+        public Composed applyEffectStrength(int effectStrength) {
+            int segmentCount = mSegments.size();
+            ArrayList<VibrationEffectSegment> scaledSegments = new ArrayList<>(segmentCount);
+            for (int i = 0; i < segmentCount; i++) {
+                scaledSegments.add(mSegments.get(i).applyEffectStrength(effectStrength));
             }
+            if (scaledSegments.equals(mSegments)) {
+                return this;
+            }
+            Composed scaled = new Composed(scaledSegments, mRepeatIndex);
+            scaled.validate();
+            return scaled;
         }
 
         @Override
-        public void writeToParcel(@NonNull Parcel out, int flags) {
-            out.writeInt(PARCEL_TOKEN_COMPOSITION);
-            out.writeList(mPrimitiveEffects);
+        public boolean equals(@Nullable Object o) {
+            if (!(o instanceof Composed)) {
+                return false;
+            }
+            Composed other = (Composed) o;
+            return mSegments.equals(other.mSegments) && mRepeatIndex == other.mRepeatIndex;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mSegments, mRepeatIndex);
+        }
+
+        @Override
+        public String toString() {
+            return "Composed{segments=" + mSegments
+                    + ", repeat=" + mRepeatIndex
+                    + "}";
         }
 
         @Override
@@ -1063,34 +676,20 @@
         }
 
         @Override
-        public boolean equals(@Nullable Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            Composed composed = (Composed) o;
-            return mPrimitiveEffects.equals(composed.mPrimitiveEffects);
+        public void writeToParcel(@NonNull Parcel out, int flags) {
+            out.writeList(mSegments);
+            out.writeInt(mRepeatIndex);
         }
 
-        @Override
-        public int hashCode() {
-            return Objects.hash(mPrimitiveEffects);
-        }
-
-        @Override
-        public String toString() {
-            return "Composed{mPrimitiveEffects=" + mPrimitiveEffects + '}';
-        }
-
-        public static final @NonNull Parcelable.Creator<Composed> CREATOR =
-                new Parcelable.Creator<Composed>() {
+        @NonNull
+        public static final Creator<Composed> CREATOR =
+                new Creator<Composed>() {
                     @Override
-                    public Composed createFromParcel(@NonNull Parcel in) {
-                        // Skip the type token
-                        in.readInt();
+                    public Composed createFromParcel(Parcel in) {
                         return new Composed(in);
                     }
 
                     @Override
-                    @NonNull
                     public Composed[] newArray(int size) {
                         return new Composed[size];
                     }
@@ -1115,7 +714,7 @@
                 PRIMITIVE_LOW_TICK,
         })
         @Retention(RetentionPolicy.SOURCE)
-        public @interface Primitive {}
+        public @interface PrimitiveType {}
 
         /**
          * No haptic effect. Used to generate extended delays between primitives.
@@ -1166,9 +765,10 @@
         public static final int PRIMITIVE_LOW_TICK = 8;
 
 
-        private ArrayList<PrimitiveEffect> mEffects = new ArrayList<>();
+        private final ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>();
+        private int mRepeatIndex = -1;
 
-        Composition() { }
+        Composition() {}
 
         /**
          * Add a haptic primitive to the end of the current composition.
@@ -1181,9 +781,8 @@
          * @return The {@link Composition} object to enable adding multiple primitives in one chain.
          */
         @NonNull
-        public Composition addPrimitive(@Primitive int primitiveId) {
-            addPrimitive(primitiveId, /*scale*/ 1.0f, /*delay*/ 0);
-            return this;
+        public Composition addPrimitive(@PrimitiveType int primitiveId) {
+            return addPrimitive(primitiveId, /*scale*/ 1.0f, /*delay*/ 0);
         }
 
         /**
@@ -1197,10 +796,9 @@
          * @return The {@link Composition} object to enable adding multiple primitives in one chain.
          */
         @NonNull
-        public Composition addPrimitive(@Primitive int primitiveId,
+        public Composition addPrimitive(@PrimitiveType int primitiveId,
                 @FloatRange(from = 0f, to = 1f) float scale) {
-            addPrimitive(primitiveId, scale, /*delay*/ 0);
-            return this;
+            return addPrimitive(primitiveId, scale, /*delay*/ 0);
         }
 
         /**
@@ -1213,9 +811,21 @@
          * @return The {@link Composition} object to enable adding multiple primitives in one chain.
          */
         @NonNull
-        public Composition addPrimitive(@Primitive int primitiveId,
+        public Composition addPrimitive(@PrimitiveType int primitiveId,
                 @FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay) {
-            mEffects.add(new PrimitiveEffect(checkPrimitive(primitiveId), scale, delay));
+            PrimitiveSegment primitive = new PrimitiveSegment(primitiveId, scale,
+                    delay);
+            primitive.validate();
+            return addSegment(primitive);
+        }
+
+        private Composition addSegment(VibrationEffectSegment segment) {
+            if (mRepeatIndex >= 0) {
+                throw new IllegalStateException(
+                        "Composition already have a repeating effect so any new primitive would be"
+                                + " unreachable.");
+            }
+            mSegments.add(segment);
             return this;
         }
 
@@ -1230,22 +840,13 @@
          */
         @NonNull
         public VibrationEffect compose() {
-            if (mEffects.isEmpty()) {
+            if (mSegments.isEmpty()) {
                 throw new IllegalStateException(
                         "Composition must have at least one element to compose.");
             }
-            return new VibrationEffect.Composed(mEffects);
-        }
-
-        /**
-         * @throws IllegalArgumentException throws if the primitive ID is not within the valid range
-         * @hide
-         *
-         */
-        static int checkPrimitive(int primitiveId) {
-            Preconditions.checkArgumentInRange(primitiveId, PRIMITIVE_NOOP, PRIMITIVE_LOW_TICK,
-                    "primitiveId");
-            return primitiveId;
+            VibrationEffect effect = new Composed(mSegments, mRepeatIndex);
+            effect.validate();
+            return effect;
         }
 
         /**
@@ -1254,7 +855,7 @@
          * @return The ID in a human readable format.
          * @hide
          */
-        public static String primitiveToString(@Primitive int id) {
+        public static String primitiveToString(@PrimitiveType int id) {
             switch (id) {
                 case PRIMITIVE_NOOP:
                     return "PRIMITIVE_NOOP";
@@ -1278,90 +879,14 @@
                     return Integer.toString(id);
             }
         }
-
-
-        /**
-         * @hide
-         */
-        public static class PrimitiveEffect implements Parcelable {
-            public int id;
-            public float scale;
-            public int delay;
-
-            PrimitiveEffect(int id, float scale, int delay) {
-                this.id = id;
-                this.scale = scale;
-                this.delay = delay;
-            }
-
-            @Override
-            public void writeToParcel(Parcel dest, int flags) {
-                dest.writeInt(id);
-                dest.writeFloat(scale);
-                dest.writeInt(delay);
-            }
-
-            @Override
-            public int describeContents() {
-                return 0;
-            }
-
-            @Override
-            public String toString() {
-                return "PrimitiveEffect{"
-                        + "id=" + primitiveToString(id)
-                        + ", scale=" + scale
-                        + ", delay=" + delay
-                        + '}';
-            }
-
-            @Override
-            public boolean equals(@Nullable Object o) {
-                if (this == o) return true;
-                if (o == null || getClass() != o.getClass()) return false;
-                PrimitiveEffect that = (PrimitiveEffect) o;
-                return id == that.id
-                        && Float.compare(that.scale, scale) == 0
-                        && delay == that.delay;
-            }
-
-            @Override
-            public int hashCode() {
-                return Objects.hash(id, scale, delay);
-            }
-
-
-            public static final @NonNull Parcelable.Creator<PrimitiveEffect> CREATOR =
-                    new Parcelable.Creator<PrimitiveEffect>() {
-                        @Override
-                        public PrimitiveEffect createFromParcel(Parcel in) {
-                            return new PrimitiveEffect(in.readInt(), in.readFloat(), in.readInt());
-                        }
-                        @Override
-                        public PrimitiveEffect[] newArray(int size) {
-                            return new PrimitiveEffect[size];
-                        }
-                    };
-        }
     }
 
-    public static final @NonNull Parcelable.Creator<VibrationEffect> CREATOR =
+    @NonNull
+    public static final Parcelable.Creator<VibrationEffect> CREATOR =
             new Parcelable.Creator<VibrationEffect>() {
                 @Override
                 public VibrationEffect createFromParcel(Parcel in) {
-                    int token = in.readInt();
-                    if (token == PARCEL_TOKEN_ONE_SHOT) {
-                        return new OneShot(in);
-                    } else if (token == PARCEL_TOKEN_WAVEFORM) {
-                        return new Waveform(in);
-                    } else if (token == PARCEL_TOKEN_EFFECT) {
-                        return new Prebaked(in);
-                    } else if (token == PARCEL_TOKEN_COMPOSITION) {
-                        return new Composed(in);
-                    } else {
-                        throw new IllegalStateException(
-                                "Unexpected vibration event type token in parcel.");
-                    }
+                    return new Composed(in);
                 }
                 @Override
                 public VibrationEffect[] newArray(int size) {
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index b90d438..a0f70c8 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -467,7 +467,7 @@
      */
     @NonNull
     public boolean[] arePrimitivesSupported(
-            @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
+            @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) {
         return new boolean[primitiveIds.length];
     }
 
@@ -478,7 +478,7 @@
      * @return Whether primitives effects are supported.
      */
     public final boolean areAllPrimitivesSupported(
-            @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
+            @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) {
         for (boolean supported : arePrimitivesSupported(primitiveIds)) {
             if (!supported) {
                 return false;
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 3121b95..9c46bc9 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -153,7 +153,8 @@
      * @param primitiveId Which primitives to query for.
      * @return Whether the primitive is supported.
      */
-    public boolean isPrimitiveSupported(@VibrationEffect.Composition.Primitive int primitiveId) {
+    public boolean isPrimitiveSupported(
+            @VibrationEffect.Composition.PrimitiveType int primitiveId) {
         return hasCapability(IVibrator.CAP_COMPOSE_EFFECTS) && mSupportedPrimitives != null
                 && mSupportedPrimitives.get(primitiveId, false);
     }
diff --git a/core/java/android/os/vibrator/PrebakedSegment.java b/core/java/android/os/vibrator/PrebakedSegment.java
new file mode 100644
index 0000000..78b4346
--- /dev/null
+++ b/core/java/android/os/vibrator/PrebakedSegment.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.VibrationEffect;
+
+import java.util.Objects;
+
+/**
+ * Representation of {@link VibrationEffectSegment} that plays a prebaked vibration effect.
+ *
+ * @hide
+ */
+@TestApi
+public final class PrebakedSegment extends VibrationEffectSegment {
+    private final int mEffectId;
+    private final boolean mFallback;
+    private final int mEffectStrength;
+
+    PrebakedSegment(@NonNull Parcel in) {
+        mEffectId = in.readInt();
+        mFallback = in.readByte() != 0;
+        mEffectStrength = in.readInt();
+    }
+
+    /** @hide */
+    public PrebakedSegment(int effectId, boolean shouldFallback, int effectStrength) {
+        mEffectId = effectId;
+        mFallback = shouldFallback;
+        mEffectStrength = effectStrength;
+    }
+
+    public int getEffectId() {
+        return mEffectId;
+    }
+
+    public int getEffectStrength() {
+        return mEffectStrength;
+    }
+
+    /** Return true if a fallback effect should be played if this effect is not supported. */
+    public boolean shouldFallback() {
+        return mFallback;
+    }
+
+    @Override
+    public long getDuration() {
+        return -1;
+    }
+
+    @Override
+    public boolean hasNonZeroAmplitude() {
+        return true;
+    }
+
+    @NonNull
+    @Override
+    public PrebakedSegment resolve(int defaultAmplitude) {
+        return this;
+    }
+
+    @NonNull
+    @Override
+    public PrebakedSegment scale(float scaleFactor) {
+        // Prebaked effect strength cannot be scaled with this method.
+        return this;
+    }
+
+    @NonNull
+    @Override
+    public PrebakedSegment applyEffectStrength(int effectStrength) {
+        if (effectStrength != mEffectStrength && isValidEffectStrength(effectStrength)) {
+            return new PrebakedSegment(mEffectId, mFallback, effectStrength);
+        }
+        return this;
+    }
+
+    private static boolean isValidEffectStrength(int strength) {
+        switch (strength) {
+            case VibrationEffect.EFFECT_STRENGTH_LIGHT:
+            case VibrationEffect.EFFECT_STRENGTH_MEDIUM:
+            case VibrationEffect.EFFECT_STRENGTH_STRONG:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    public void validate() {
+        switch (mEffectId) {
+            case VibrationEffect.EFFECT_CLICK:
+            case VibrationEffect.EFFECT_DOUBLE_CLICK:
+            case VibrationEffect.EFFECT_TICK:
+            case VibrationEffect.EFFECT_TEXTURE_TICK:
+            case VibrationEffect.EFFECT_THUD:
+            case VibrationEffect.EFFECT_POP:
+            case VibrationEffect.EFFECT_HEAVY_CLICK:
+                break;
+            default:
+                int[] ringtones = VibrationEffect.RINGTONES;
+                if (mEffectId < ringtones[0] || mEffectId > ringtones[ringtones.length - 1]) {
+                    throw new IllegalArgumentException(
+                            "Unknown prebaked effect type (value=" + mEffectId + ")");
+                }
+        }
+        if (!isValidEffectStrength(mEffectStrength)) {
+            throw new IllegalArgumentException(
+                    "Unknown prebaked effect strength (value=" + mEffectStrength + ")");
+        }
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (!(o instanceof PrebakedSegment)) {
+            return false;
+        }
+        PrebakedSegment other = (PrebakedSegment) o;
+        return mEffectId == other.mEffectId
+                && mFallback == other.mFallback
+                && mEffectStrength == other.mEffectStrength;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mEffectId, mFallback, mEffectStrength);
+    }
+
+    @Override
+    public String toString() {
+        return "Prebaked{effect=" + VibrationEffect.effectIdToString(mEffectId)
+                + ", strength=" + VibrationEffect.effectStrengthToString(mEffectStrength)
+                + ", fallback=" + mFallback
+                + "}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeInt(PARCEL_TOKEN_PREBAKED);
+        out.writeInt(mEffectId);
+        out.writeByte((byte) (mFallback ? 1 : 0));
+        out.writeInt(mEffectStrength);
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<PrebakedSegment> CREATOR =
+            new Parcelable.Creator<PrebakedSegment>() {
+                @Override
+                public PrebakedSegment createFromParcel(Parcel in) {
+                    // Skip the type token
+                    in.readInt();
+                    return new PrebakedSegment(in);
+                }
+
+                @Override
+                public PrebakedSegment[] newArray(int size) {
+                    return new PrebakedSegment[size];
+                }
+            };
+}
diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java
new file mode 100644
index 0000000..2ef29cb
--- /dev/null
+++ b/core/java/android/os/vibrator/PrimitiveSegment.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.VibrationEffect;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Representation of {@link VibrationEffectSegment} that plays a primitive vibration effect after a
+ * specified delay and applying a given scale.
+ *
+ * @hide
+ */
+@TestApi
+public final class PrimitiveSegment extends VibrationEffectSegment {
+    private final int mPrimitiveId;
+    private final float mScale;
+    private final int mDelay;
+
+    PrimitiveSegment(@NonNull Parcel in) {
+        this(in.readInt(), in.readFloat(), in.readInt());
+    }
+
+    /** @hide */
+    public PrimitiveSegment(int id, float scale, int delay) {
+        mPrimitiveId = id;
+        mScale = scale;
+        mDelay = delay;
+    }
+
+    public int getPrimitiveId() {
+        return mPrimitiveId;
+    }
+
+    public float getScale() {
+        return mScale;
+    }
+
+    public int getDelay() {
+        return mDelay;
+    }
+
+    @Override
+    public long getDuration() {
+        return -1;
+    }
+
+    @Override
+    public boolean hasNonZeroAmplitude() {
+        // Every primitive plays a vibration with a non-zero amplitude, even at scale == 0.
+        return true;
+    }
+
+    @NonNull
+    @Override
+    public PrimitiveSegment resolve(int defaultAmplitude) {
+        return this;
+    }
+
+    @NonNull
+    @Override
+    public PrimitiveSegment scale(float scaleFactor) {
+        return new PrimitiveSegment(mPrimitiveId, VibrationEffect.scale(mScale, scaleFactor),
+                mDelay);
+    }
+
+    @NonNull
+    @Override
+    public PrimitiveSegment applyEffectStrength(int effectStrength) {
+        return this;
+    }
+
+    @Override
+    public void validate() {
+        Preconditions.checkArgumentInRange(mPrimitiveId, VibrationEffect.Composition.PRIMITIVE_NOOP,
+                VibrationEffect.Composition.PRIMITIVE_LOW_TICK, "primitiveId");
+        Preconditions.checkArgumentInRange(mScale, 0f, 1f, "scale");
+        Preconditions.checkArgumentNonnegative(mDelay, "primitive delay should be >= 0");
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(PARCEL_TOKEN_PRIMITIVE);
+        dest.writeInt(mPrimitiveId);
+        dest.writeFloat(mScale);
+        dest.writeInt(mDelay);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "Primitive{"
+                + "primitive=" + VibrationEffect.Composition.primitiveToString(mPrimitiveId)
+                + ", scale=" + mScale
+                + ", delay=" + mDelay
+                + '}';
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        PrimitiveSegment that = (PrimitiveSegment) o;
+        return mPrimitiveId == that.mPrimitiveId
+                && Float.compare(that.mScale, mScale) == 0
+                && mDelay == that.mDelay;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPrimitiveId, mScale, mDelay);
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<PrimitiveSegment> CREATOR =
+            new Parcelable.Creator<PrimitiveSegment>() {
+                @Override
+                public PrimitiveSegment createFromParcel(Parcel in) {
+                    // Skip the type token
+                    in.readInt();
+                    return new PrimitiveSegment(in);
+                }
+
+                @Override
+                public PrimitiveSegment[] newArray(int size) {
+                    return new PrimitiveSegment[size];
+                }
+            };
+}
diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java
new file mode 100644
index 0000000..61a5d6c
--- /dev/null
+++ b/core/java/android/os/vibrator/StepSegment.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.VibrationEffect;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Representation of {@link VibrationEffectSegment} that holds a fixed vibration amplitude for a
+ * specified duration.
+ *
+ * @hide
+ */
+@TestApi
+public final class StepSegment extends VibrationEffectSegment {
+    private final float mAmplitude;
+    private final int mDuration;
+
+    StepSegment(@NonNull Parcel in) {
+        this(in.readFloat(), in.readInt());
+    }
+
+    /** @hide */
+    public StepSegment(float amplitude, int duration) {
+        mAmplitude = amplitude;
+        mDuration = duration;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof StepSegment)) {
+            return false;
+        }
+        StepSegment other = (StepSegment) o;
+        return Float.compare(mAmplitude, other.mAmplitude) == 0
+                && mDuration == other.mDuration;
+    }
+
+    public float getAmplitude() {
+        return mAmplitude;
+    }
+
+    @Override
+    public long getDuration() {
+        return mDuration;
+    }
+
+    @Override
+    public boolean hasNonZeroAmplitude() {
+        // DEFAULT_AMPLITUDE == -1 is still a non-zero amplitude that will be resolved later.
+        return Float.compare(mAmplitude, 0) != 0;
+    }
+
+    @Override
+    public void validate() {
+        Preconditions.checkArgumentNonnegative(mDuration,
+                "Durations must all be >= 0, got " + mDuration);
+        if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) != 0) {
+            Preconditions.checkArgumentInRange(mAmplitude, 0f, 1f, "amplitude");
+        }
+    }
+
+    @NonNull
+    @Override
+    public StepSegment resolve(int defaultAmplitude) {
+        if (defaultAmplitude > VibrationEffect.MAX_AMPLITUDE || defaultAmplitude <= 0) {
+            throw new IllegalArgumentException(
+                    "amplitude must be between 1 and 255 inclusive (amplitude="
+                            + defaultAmplitude + ")");
+        }
+        if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) != 0) {
+            return this;
+        }
+        return new StepSegment((float) defaultAmplitude / VibrationEffect.MAX_AMPLITUDE, mDuration);
+    }
+
+    @NonNull
+    @Override
+    public StepSegment scale(float scaleFactor) {
+        if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0) {
+            return this;
+        }
+        return new StepSegment(VibrationEffect.scale(mAmplitude, scaleFactor), mDuration);
+    }
+
+    @NonNull
+    @Override
+    public StepSegment applyEffectStrength(int effectStrength) {
+        return this;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mAmplitude, mDuration);
+    }
+
+    @Override
+    public String toString() {
+        return "Step{amplitude=" + mAmplitude
+                + ", duration=" + mDuration
+                + "}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeInt(PARCEL_TOKEN_STEP);
+        out.writeFloat(mAmplitude);
+        out.writeInt(mDuration);
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<StepSegment> CREATOR =
+            new Parcelable.Creator<StepSegment>() {
+                @Override
+                public StepSegment createFromParcel(Parcel in) {
+                    // Skip the type token
+                    in.readInt();
+                    return new StepSegment(in);
+                }
+
+                @Override
+                public StepSegment[] newArray(int size) {
+                    return new StepSegment[size];
+                }
+            };
+}
diff --git a/core/java/android/os/vibrator/VibrationEffectSegment.java b/core/java/android/os/vibrator/VibrationEffectSegment.java
new file mode 100644
index 0000000..3dc9e12
--- /dev/null
+++ b/core/java/android/os/vibrator/VibrationEffectSegment.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.VibrationEffect;
+
+/**
+ * Representation of a single segment of a {@link VibrationEffect}.
+ *
+ * <p>Vibration effects are represented as a sequence of segments that describes how vibration
+ * amplitude and frequency changes over time. Segments can be described as one of the following:
+ *
+ * <ol>
+ *     <li>A predefined vibration effect;
+ *     <li>A composable effect primitive;
+ *     <li>Fixed amplitude value to be held for a specified duration;
+ * </ol>
+ *
+ * @hide
+ */
+@TestApi
+@SuppressWarnings({"ParcelNotFinal", "ParcelCreator"}) // Parcel only extended here.
+public abstract class VibrationEffectSegment implements Parcelable {
+    static final int PARCEL_TOKEN_PREBAKED = 1;
+    static final int PARCEL_TOKEN_PRIMITIVE = 2;
+    static final int PARCEL_TOKEN_STEP = 3;
+
+    /** Prevent subclassing from outside of this package */
+    VibrationEffectSegment() {
+    }
+
+    /**
+     * Gets the estimated duration of the segment in milliseconds.
+     *
+     * <p>For segments with an unknown duration (e.g. prebaked or primitive effects where the length
+     * is device and potentially run-time dependent), this returns -1.
+     */
+    public abstract long getDuration();
+
+    /** Returns true if this segment plays at a non-zero amplitude at some point. */
+    public abstract boolean hasNonZeroAmplitude();
+
+    /** Validates the segment, throwing exceptions if any parameter is invalid. */
+    public abstract void validate();
+
+    /**
+     * Resolves amplitudes set to {@link VibrationEffect#DEFAULT_AMPLITUDE}.
+     *
+     * <p>This might fail with {@link IllegalArgumentException} if value is non-positive or larger
+     * than {@link VibrationEffect#MAX_AMPLITUDE}.
+     */
+    @NonNull
+    public abstract <T extends VibrationEffectSegment> T resolve(int defaultAmplitude);
+
+    /**
+     * Scale the segment intensity with the given factor.
+     *
+     * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
+     *                    scale down the intensity, values larger than 1 will scale up
+     */
+    @NonNull
+    public abstract <T extends VibrationEffectSegment> T scale(float scaleFactor);
+
+    /**
+     * Applies given effect strength to prebaked effects.
+     *
+     * @param effectStrength new effect strength to be applied, one of
+     *                       VibrationEffect.EFFECT_STRENGTH_*.
+     */
+    @NonNull
+    public abstract <T extends VibrationEffectSegment> T applyEffectStrength(int effectStrength);
+
+    @NonNull
+    public static final Creator<VibrationEffectSegment> CREATOR =
+            new Creator<VibrationEffectSegment>() {
+                @Override
+                public VibrationEffectSegment createFromParcel(Parcel in) {
+                    switch (in.readInt()) {
+                        case PARCEL_TOKEN_STEP:
+                            return new StepSegment(in);
+                        case PARCEL_TOKEN_PREBAKED:
+                            return new PrebakedSegment(in);
+                        case PARCEL_TOKEN_PRIMITIVE:
+                            return new PrimitiveSegment(in);
+                        default:
+                            throw new IllegalStateException(
+                                    "Unexpected vibration event type token in parcel.");
+                    }
+                }
+
+                @Override
+                public VibrationEffectSegment[] newArray(int size) {
+                    return new VibrationEffectSegment[size];
+                }
+            };
+}
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index e68f330..7e40497 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -358,6 +358,38 @@
     public static final String NAMESPACE_SETTINGS_STATS = "settings_stats";
 
     /**
+     * Namespace for all statsd java features that can be applied immediately.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String NAMESPACE_STATSD_JAVA = "statsd_java";
+
+    /**
+     * Namespace for all statsd java features that are applied on boot.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String NAMESPACE_STATSD_JAVA_BOOT = "statsd_java_boot";
+
+    /**
+     * Namespace for all statsd native features that can be applied immediately.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String NAMESPACE_STATSD_NATIVE = "statsd_native";
+
+    /**
+     * Namespace for all statsd native features that are applied on boot.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String NAMESPACE_STATSD_NATIVE_BOOT = "statsd_native_boot";
+
+    /**
      * Namespace for storage-related features.
      *
      * @deprecated Replace storage namespace with storage_native_boot.
@@ -456,7 +488,8 @@
      */
     @NonNull
     private static final List<String> PUBLIC_NAMESPACES =
-            Arrays.asList(NAMESPACE_TEXTCLASSIFIER, NAMESPACE_RUNTIME);
+            Arrays.asList(NAMESPACE_TEXTCLASSIFIER, NAMESPACE_RUNTIME, NAMESPACE_STATSD_JAVA,
+                    NAMESPACE_STATSD_JAVA_BOOT);
     /**
      * Privacy related properties definitions.
      *
@@ -505,38 +538,6 @@
             "connectivity_thermal_power_manager";
 
     /**
-     * Namespace for all statsd java features that can be applied immediately.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_STATSD_JAVA = "statsd_java";
-
-    /**
-     * Namespace for all statsd java features that are applied on boot.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_STATSD_JAVA_BOOT = "statsd_java_boot";
-
-    /**
-     * Namespace for all statsd native features that can be applied immediately.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_STATSD_NATIVE = "statsd_native";
-
-    /**
-     * Namespace for all statsd native features that are applied on boot.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_STATSD_NATIVE_BOOT = "statsd_native_boot";
-
-    /**
      * Namespace for configuration related features.
      *
      * @hide
diff --git a/core/java/android/service/notification/ScheduleCalendar.java b/core/java/android/service/notification/ScheduleCalendar.java
index 6ed966e..314c97d 100644
--- a/core/java/android/service/notification/ScheduleCalendar.java
+++ b/core/java/android/service/notification/ScheduleCalendar.java
@@ -57,25 +57,18 @@
     }
 
     /**
-     * Sets next alarm of the schedule if the saved next alarm has passed or is further
-     * in the future than given nextAlarm
+     * Sets next alarm of the schedule
      * @param now current time in milliseconds
      * @param nextAlarm time of next alarm in milliseconds
      */
     public void maybeSetNextAlarm(long now, long nextAlarm) {
         if (mSchedule != null && mSchedule.exitAtAlarm) {
-            // alarm canceled
             if (nextAlarm == 0) {
+                // alarm canceled
                 mSchedule.nextAlarm = 0;
-            }
-            // only allow alarms in the future
-            if (nextAlarm > now) {
-                if (mSchedule.nextAlarm == 0 || mSchedule.nextAlarm < now) {
-                    mSchedule.nextAlarm = nextAlarm;
-                } else {
-                    // store earliest alarm
-                    mSchedule.nextAlarm = Math.min(mSchedule.nextAlarm, nextAlarm);
-                }
+            } else if (nextAlarm > now) {
+                // only allow alarms in the future
+                mSchedule.nextAlarm = nextAlarm;
             } else if (mSchedule.nextAlarm < now) {
                 if (DEBUG) {
                     Log.d(TAG, "All alarms are in the past " + mSchedule.nextAlarm);
diff --git a/core/java/android/service/translation/ITranslationService.aidl b/core/java/android/service/translation/ITranslationService.aidl
index 8d798c3..297f00a4 100644
--- a/core/java/android/service/translation/ITranslationService.aidl
+++ b/core/java/android/service/translation/ITranslationService.aidl
@@ -16,7 +16,8 @@
 
 package android.service.translation;
 
-import android.view.translation.TranslationSpec;
+import android.os.ResultReceiver;
+import android.view.translation.TranslationContext;
 import com.android.internal.os.IResultReceiver;
 
 /**
@@ -31,6 +32,9 @@
 oneway interface ITranslationService {
     void onConnected();
     void onDisconnected();
-    void onCreateTranslationSession(in TranslationSpec sourceSpec, in TranslationSpec destSpec,
-         int sessionId, in IResultReceiver receiver);
+    void onCreateTranslationSession(in TranslationContext translationContext, int sessionId,
+         in IResultReceiver receiver);
+
+    void onTranslationCapabilitiesRequest(int sourceFormat, int targetFormat,
+         in ResultReceiver receiver);
 }
diff --git a/core/java/android/service/translation/TranslationService.java b/core/java/android/service/translation/TranslationService.java
index c5f1f04..7edf2e2 100644
--- a/core/java/android/service/translation/TranslationService.java
+++ b/core/java/android/service/translation/TranslationService.java
@@ -34,8 +34,12 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.util.ArraySet;
 import android.util.Log;
 import android.view.translation.ITranslationDirectManager;
+import android.view.translation.TranslationCapability;
+import android.view.translation.TranslationContext;
 import android.view.translation.TranslationManager;
 import android.view.translation.TranslationRequest;
 import android.view.translation.TranslationResponse;
@@ -43,6 +47,9 @@
 
 import com.android.internal.os.IResultReceiver;
 
+import java.util.Set;
+import java.util.function.Consumer;
+
 /**
  * Service for translating text.
  * @hide
@@ -92,10 +99,20 @@
         }
 
         @Override
-        public void onCreateTranslationSession(TranslationSpec sourceSpec, TranslationSpec destSpec,
+        public void onCreateTranslationSession(TranslationContext translationContext,
                 int sessionId, IResultReceiver receiver) throws RemoteException {
             mHandler.sendMessage(obtainMessage(TranslationService::handleOnCreateTranslationSession,
-                    TranslationService.this, sourceSpec, destSpec, sessionId, receiver));
+                    TranslationService.this, translationContext, sessionId, receiver));
+        }
+
+        @Override
+        public void onTranslationCapabilitiesRequest(@TranslationSpec.DataFormat int sourceFormat,
+                @TranslationSpec.DataFormat int targetFormat,
+                @NonNull ResultReceiver resultReceiver) throws RemoteException {
+            mHandler.sendMessage(
+                    obtainMessage(TranslationService::handleOnTranslationCapabilitiesRequest,
+                            TranslationService.this, sourceFormat, targetFormat,
+                            resultReceiver));
         }
     };
 
@@ -194,14 +211,13 @@
     /**
      * TODO: fill in javadoc.
      *
-     * @param sourceSpec
-     * @param destSpec
+     * @param translationContext
      * @param sessionId
      */
     // TODO(b/176464808): the session id won't be unique cross client/server process. Need to find
     // solution to make it's safe.
-    public abstract void onCreateTranslationSession(@NonNull TranslationSpec sourceSpec,
-            @NonNull TranslationSpec destSpec, int sessionId);
+    public abstract void onCreateTranslationSession(@NonNull TranslationContext translationContext,
+            int sessionId);
 
     /**
      * TODO: fill in javadoc.
@@ -222,12 +238,27 @@
             @NonNull CancellationSignal cancellationSignal,
             @NonNull OnTranslationResultCallback callback);
 
+    /**
+     * TODO: fill in javadoc
+     * TODO: make this abstract again once aiai is ready.
+     *
+     * <p>Must call {@code callback.accept} to pass back the set of translation capabilities.</p>
+     *
+     * @param sourceFormat
+     * @param targetFormat
+     * @param callback
+     */
+    public abstract void onTranslationCapabilitiesRequest(
+            @TranslationSpec.DataFormat int sourceFormat,
+            @TranslationSpec.DataFormat int targetFormat,
+            @NonNull Consumer<Set<TranslationCapability>> callback);
+
     // TODO(b/176464808): Need to handle client dying case
 
-    // TODO(b/176464808): Need to handle the failure case. e.g. if the specs does not support
+    // TODO(b/176464808): Need to handle the failure case. e.g. if the context is not supported.
 
-    private void handleOnCreateTranslationSession(@NonNull TranslationSpec sourceSpec,
-            @NonNull TranslationSpec destSpec, int sessionId, IResultReceiver resultReceiver) {
+    private void handleOnCreateTranslationSession(@NonNull TranslationContext translationContext,
+            int sessionId, IResultReceiver resultReceiver) {
         try {
             final Bundle extras = new Bundle();
             extras.putBinder(EXTRA_SERVICE_BINDER, mClientInterface.asBinder());
@@ -236,6 +267,24 @@
         } catch (RemoteException e) {
             Log.w(TAG, "RemoteException sending client interface: " + e);
         }
-        onCreateTranslationSession(sourceSpec, destSpec, sessionId);
+        onCreateTranslationSession(translationContext, sessionId);
+    }
+
+    private void handleOnTranslationCapabilitiesRequest(
+            @TranslationSpec.DataFormat int sourceFormat,
+            @TranslationSpec.DataFormat int targetFormat,
+            @NonNull ResultReceiver resultReceiver) {
+        onTranslationCapabilitiesRequest(sourceFormat, targetFormat,
+                new Consumer<Set<TranslationCapability>>() {
+                    @Override
+                    public void accept(Set<TranslationCapability> values) {
+                        final ArraySet<TranslationCapability> capabilities = new ArraySet<>(values);
+                        final Bundle bundle = new Bundle();
+                        bundle.putParcelableArray(TranslationManager.EXTRA_CAPABILITIES,
+                                capabilities.toArray(new TranslationCapability[0]));
+                        resultReceiver.send(TranslationManager.STATUS_SYNC_CALL_SUCCESS, bundle);
+                    }
+                });
+
     }
 }
diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java
index d3eaeae..2a43222 100644
--- a/core/java/android/util/Slog.java
+++ b/core/java/android/util/Slog.java
@@ -26,6 +26,10 @@
 import java.util.Locale;
 
 /**
+ * API for sending log output to the {@link Log#LOG_ID_SYSTEM} buffer.
+ *
+ * <p>Should be used by system components. Use {@code adb logcat --buffer=system} to fetch the logs.
+ *
  * @hide
  */
 public final class Slog {
@@ -51,6 +55,12 @@
 
     /**
      * Logs a {@link Log.VERBOSE} message.
+     *
+     * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+     * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+     * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+     * calling this method in a critical path, make sure to explicitly do the check before calling
+     * it.
      */
     public static void v(String tag, String format, @Nullable Object... args) {
         if (!Log.isLoggable(tag, Log.VERBOSE)) return;
@@ -71,6 +81,12 @@
 
     /**
      * Logs a {@link Log.DEBUG} message.
+     *
+     * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+     * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+     * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+     * calling this method in a critical path, make sure to explicitly do the check before calling
+     * it.
      */
     public static void d(String tag, String format, @Nullable Object... args) {
         if (!Log.isLoggable(tag, Log.DEBUG)) return;
@@ -90,6 +106,12 @@
 
     /**
      * Logs a {@link Log.INFO} message.
+     *
+     * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+     * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+     * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+     * calling this method in a critical path, make sure to explicitly do the check before calling
+     * it.
      */
     public static void i(String tag, String format, @Nullable Object... args) {
         if (!Log.isLoggable(tag, Log.INFO)) return;
@@ -114,6 +136,12 @@
 
     /**
      * Logs a {@link Log.WARN} message.
+     *
+     * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+     * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+     * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+     * calling this method in a critical path, make sure to explicitly do the check before calling
+     * it.
      */
     public static void w(String tag, String format, @Nullable Object... args) {
         if (!Log.isLoggable(tag, Log.WARN)) return;
@@ -123,6 +151,12 @@
 
     /**
      * Logs a {@link Log.WARN} message with an exception
+     *
+     * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+     * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+     * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+     * calling this method in a critical path, make sure to explicitly do the check before calling
+     * it.
      */
     public static void w(String tag, Exception exception, String format, @Nullable Object... args) {
         if (!Log.isLoggable(tag, Log.WARN)) return;
@@ -143,6 +177,12 @@
 
     /**
      * Logs a {@link Log.ERROR} message.
+     *
+     * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+     * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+     * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+     * calling this method in a critical path, make sure to explicitly do the check before calling
+     * it.
      */
     public static void e(String tag, String format, @Nullable Object... args) {
         if (!Log.isLoggable(tag, Log.ERROR)) return;
@@ -152,6 +192,12 @@
 
     /**
      * Logs a {@link Log.ERROR} message with an exception
+     *
+     * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+     * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+     * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+     * calling this method in a critical path, make sure to explicitly do the check before calling
+     * it.
      */
     public static void e(String tag, Exception exception, String format, @Nullable Object... args) {
         if (!Log.isLoggable(tag, Log.ERROR)) return;
diff --git a/core/java/android/util/SparseDoubleArray.java b/core/java/android/util/SparseDoubleArray.java
new file mode 100644
index 0000000..dc93a47
--- /dev/null
+++ b/core/java/android/util/SparseDoubleArray.java
@@ -0,0 +1,166 @@
+/*
+ * 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.util;
+
+/**
+ * SparseDoubleArrays map integers to doubles.  Unlike a normal array of doubles,
+ * there can be gaps in the indices.  It is intended to be more memory efficient
+ * than using a HashMap to map Integers to Doubles, both because it avoids
+ * auto-boxing keys and values and its data structure doesn't rely on an extra entry object
+ * for each mapping.
+ *
+ * <p>Note that this container keeps its mappings in an array data structure,
+ * using a binary search to find keys.  The implementation is not intended to be appropriate for
+ * data structures
+ * that may contain large numbers of items.  It is generally slower than a traditional
+ * HashMap, since lookups require a binary search and adds and removes require inserting
+ * and deleting entries in the array.  For containers holding up to hundreds of items,
+ * the performance difference is not significant, less than 50%.</p>
+ *
+ * <p>It is possible to iterate over the items in this container using
+ * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using
+ * <code>keyAt(int)</code> with ascending values of the index will return the
+ * keys in ascending order, or the values corresponding to the keys in ascending
+ * order in the case of <code>valueAt(int)</code>.</p>
+ *
+ * @see SparseLongArray
+ *
+ * @hide
+ */
+public class SparseDoubleArray implements Cloneable {
+    /**
+     * The int->double map, but storing the doubles as longs using
+     * {@link Double#doubleToRawLongBits(double)}.
+     */
+    private SparseLongArray mValues;
+
+    /** Creates a new SparseDoubleArray containing no mappings. */
+    public SparseDoubleArray() {
+        this(10);
+    }
+
+    /**
+     * Creates a new SparseDoubleArray, containing no mappings, that will not
+     * require any additional memory allocation to store the specified
+     * number of mappings.  If you supply an initial capacity of 0, the
+     * sparse array will be initialized with a light-weight representation
+     * not requiring any additional array allocations.
+     */
+    public SparseDoubleArray(int initialCapacity) {
+        mValues = new SparseLongArray(initialCapacity);
+    }
+
+    @Override
+    public SparseDoubleArray clone() {
+        SparseDoubleArray clone = null;
+        try {
+            clone = (SparseDoubleArray) super.clone();
+            clone.mValues = mValues.clone();
+        } catch (CloneNotSupportedException cnse) {
+            /* ignore */
+        }
+        return clone;
+    }
+
+    /**
+     * Gets the double mapped from the specified key, or <code>0</code>
+     * if no such mapping has been made.
+     */
+    public double get(int key) {
+        final int index = mValues.indexOfKey(key);
+        if (index < 0) {
+            return 0.0d;
+        }
+        return valueAt(index);
+    }
+
+    /**
+     * Adds a mapping from the specified key to the specified value,
+     * replacing the previous mapping from the specified key if there
+     * was one.
+     */
+    public void put(int key, double value) {
+        mValues.put(key, Double.doubleToRawLongBits(value));
+    }
+
+    /**
+     * Adds a mapping from the specified key to the specified value,
+     * <b>adding</b> its value to the previous mapping from the specified key if there
+     * was one.
+     *
+     * <p>This differs from {@link #put} because instead of replacing any previous value, it adds
+     * (in the numerical sense) to it.
+     */
+    public void add(int key, double summand) {
+        final double oldValue = get(key);
+        put(key, oldValue + summand);
+    }
+
+    /** Returns the number of key-value mappings that this SparseDoubleArray currently stores. */
+    public int size() {
+        return mValues.size();
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the key from the <code>index</code>th key-value mapping that this
+     * SparseDoubleArray stores.
+     *
+     * @see SparseLongArray#keyAt(int)
+     */
+    public int keyAt(int index) {
+        return mValues.keyAt(index);
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the value from the <code>index</code>th key-value mapping that this
+     * SparseDoubleArray stores.
+     *
+     * @see SparseLongArray#valueAt(int)
+     */
+    public double valueAt(int index) {
+        return Double.longBitsToDouble(mValues.valueAt(index));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This implementation composes a string by iterating over its mappings.
+     */
+    @Override
+    public String toString() {
+        if (size() <= 0) {
+            return "{}";
+        }
+
+        StringBuilder buffer = new StringBuilder(size() * 34);
+        buffer.append('{');
+        for (int i = 0; i < size(); i++) {
+            if (i > 0) {
+                buffer.append(", ");
+            }
+            int key = keyAt(i);
+            buffer.append(key);
+            buffer.append('=');
+            double value = valueAt(i);
+            buffer.append(value);
+        }
+        buffer.append('}');
+        return buffer.toString();
+    }
+}
diff --git a/core/java/android/util/imetracing/ImeTracing.java b/core/java/android/util/imetracing/ImeTracing.java
index b28cfb8..2fcaec9 100644
--- a/core/java/android/util/imetracing/ImeTracing.java
+++ b/core/java/android/util/imetracing/ImeTracing.java
@@ -130,6 +130,16 @@
     public abstract void triggerManagerServiceDump(String where);
 
     /**
+     * Being called while taking a bugreport so that tracing files can be included in the bugreport
+     * when the IME tracing is running.  Does nothing otherwise.
+     *
+     * @param pw Print writer
+     */
+    public void saveForBugreport(@Nullable PrintWriter pw) {
+        // does nothing by default.
+    }
+
+    /**
      * Sets whether ime tracing is enabled.
      *
      * @param enabled Tells whether ime tracing should be enabled or disabled.
@@ -153,11 +163,6 @@
     }
 
     /**
-     * Writes the current tracing data to the specific output proto file.
-     */
-    public abstract void writeTracesToFiles();
-
-    /**
      * Starts a new IME trace if one is not already started.
      *
      * @param pw Print writer
@@ -171,14 +176,6 @@
      */
     public abstract void stopTrace(@Nullable PrintWriter pw);
 
-    /**
-     * Stops the IME trace if one was previously started.
-     *
-     * @param pw Print writer
-     * @param writeToFile If the current buffer should be written to disk or not
-     */
-    public abstract void stopTrace(@Nullable PrintWriter pw, boolean writeToFile);
-
     private static boolean isSystemProcess() {
         return ActivityThread.isSystem();
     }
diff --git a/core/java/android/util/imetracing/ImeTracingClientImpl.java b/core/java/android/util/imetracing/ImeTracingClientImpl.java
index 35a81b7..17cdc46 100644
--- a/core/java/android/util/imetracing/ImeTracingClientImpl.java
+++ b/core/java/android/util/imetracing/ImeTracingClientImpl.java
@@ -99,18 +99,10 @@
     }
 
     @Override
-    public void writeTracesToFiles() {
-    }
-
-    @Override
     public void startTrace(PrintWriter pw) {
     }
 
     @Override
     public void stopTrace(PrintWriter pw) {
     }
-
-    @Override
-    public void stopTrace(PrintWriter pw, boolean writeToFile) {
-    }
 }
diff --git a/core/java/android/util/imetracing/ImeTracingServerImpl.java b/core/java/android/util/imetracing/ImeTracingServerImpl.java
index 77f017a..06e4c50 100644
--- a/core/java/android/util/imetracing/ImeTracingServerImpl.java
+++ b/core/java/android/util/imetracing/ImeTracingServerImpl.java
@@ -139,14 +139,6 @@
         }
     }
 
-    @GuardedBy("mEnabledLock")
-    @Override
-    public void writeTracesToFiles() {
-        synchronized (mEnabledLock) {
-            writeTracesToFilesLocked();
-        }
-    }
-
     private void writeTracesToFilesLocked() {
         try {
             ProtoOutputStream clientsProto = new ProtoOutputStream();
@@ -192,12 +184,6 @@
 
     @Override
     public void stopTrace(@Nullable PrintWriter pw) {
-        stopTrace(pw, true /* writeToFile */);
-    }
-
-    @GuardedBy("mEnabledLock")
-    @Override
-    public void stopTrace(@Nullable PrintWriter pw, boolean writeToFile) {
         if (IS_USER) {
             Log.w(TAG, "Warn: Tracing is not supported on user builds.");
             return;
@@ -213,9 +199,35 @@
                     + TRACE_FILENAME_CLIENTS + ", " + TRACE_FILENAME_IMS + ", "
                     + TRACE_FILENAME_IMMS);
             sEnabled = false;
-            if (writeToFile) {
-                writeTracesToFilesLocked();
+            writeTracesToFilesLocked();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void saveForBugreport(@Nullable PrintWriter pw) {
+        if (IS_USER) {
+            return;
+        }
+        synchronized (mEnabledLock) {
+            if (!isAvailable() || !isEnabled()) {
+                return;
             }
+            // Temporarily stop accepting logs from trace event providers.  There is a small chance
+            // that we may drop some trace events while writing the file, but we currently need to
+            // live with that.  Note that addToBuffer() also has a bug that it doesn't do
+            // read-acquire so flipping sEnabled here doesn't even guarantee that addToBuffer() will
+            // temporarily stop accepting incoming events...
+            // TODO(b/175761228): Implement atomic snapshot to avoid downtime.
+            // TODO(b/175761228): Fix synchronization around sEnabled.
+            sEnabled = false;
+            logAndPrintln(pw, "Writing traces in " + TRACE_DIRNAME + ": "
+                    + TRACE_FILENAME_CLIENTS + ", " + TRACE_FILENAME_IMS + ", "
+                    + TRACE_FILENAME_IMMS);
+            writeTracesToFilesLocked();
+            sEnabled = true;
         }
     }
 
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index f4d5a7b..98b7dbf 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -50,10 +50,6 @@
         super(ITYPE_IME, state, transactionSupplier, controller);
     }
 
-    public void applyImeVisibility(boolean setVisible) {
-        mController.applyImeVisibility(setVisible);
-    }
-
     @Override
     public void onWindowFocusGained() {
         super.onWindowFocusGained();
diff --git a/core/java/android/view/InputEventAssigner.java b/core/java/android/view/InputEventAssigner.java
index c159a12..7fac6c5 100644
--- a/core/java/android/view/InputEventAssigner.java
+++ b/core/java/android/view/InputEventAssigner.java
@@ -45,13 +45,13 @@
 public class InputEventAssigner {
     private static final String TAG = "InputEventAssigner";
     private boolean mHasUnprocessedDown = false;
-    private int mEventId = INVALID_INPUT_EVENT_ID;
+    private int mDownEventId = INVALID_INPUT_EVENT_ID;
 
     /**
-     * Notify InputEventAssigner that the Choreographer callback has been processed. This will reset
-     * the 'down' state to assign the latest input event to the current frame.
+     * Notify InputEventAssigner that a frame has been processed. We no longer need to keep track of
+     * the DOWN event because a frame has already been produced for it.
      */
-    public void onChoreographerCallback() {
+    public void notifyFrameProcessed() {
         // Mark completion of this frame. Use newest input event from now on.
         mHasUnprocessedDown = false;
     }
@@ -62,31 +62,22 @@
      * @return the id of the input event to use for the current frame
      */
     public int processEvent(InputEvent event) {
-        if (event instanceof KeyEvent) {
-            // We will not do any special handling for key events
-            return event.getId();
-        }
-
         if (event instanceof MotionEvent) {
             MotionEvent motionEvent = (MotionEvent) event;
-            final int action = motionEvent.getActionMasked();
-
-            if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
-                mHasUnprocessedDown = false;
+            if (motionEvent.isFromSource(SOURCE_TOUCHSCREEN)) {
+                final int action = motionEvent.getActionMasked();
+                if (action == MotionEvent.ACTION_DOWN) {
+                    mHasUnprocessedDown = true;
+                    mDownEventId = event.getId();
+                }
+                if (mHasUnprocessedDown && action == MotionEvent.ACTION_MOVE) {
+                    return mDownEventId;
+                }
+                if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+                    mHasUnprocessedDown = false;
+                }
             }
-            if (motionEvent.isFromSource(SOURCE_TOUCHSCREEN) && action == MotionEvent.ACTION_DOWN) {
-                mHasUnprocessedDown = true;
-                mEventId = event.getId();
-                // This will remain 'true' even if we receive a MOVE event, as long as choreographer
-                // hasn't invoked the 'CALLBACK_INPUT' callback.
-            }
-            // Don't update the event id if we haven't processed DOWN yet.
-            if (!mHasUnprocessedDown) {
-                mEventId = event.getId();
-            }
-            return mEventId;
         }
-
-        throw new IllegalArgumentException("Received unexpected " + event);
+        return event.getId();
     }
 }
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 7a61df8..a67ec10 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -269,7 +269,7 @@
         }
         mShownOnFinish = shown;
         mFinished = true;
-        setInsetsAndAlpha(shown ? mShownInsets : mHiddenInsets, 1f /* alpha */, 1f /* fraction */,
+        setInsetsAndAlpha(shown ? mShownInsets : mHiddenInsets, mPendingAlpha, 1f /* fraction */,
                 true /* allowWhenFinished */);
 
         if (DEBUG) Log.d(TAG, "notify control request finished for types: " + mTypes);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 8bf78db3..a68f528 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -189,16 +189,34 @@
     }
 
     private static final String TAG = "InsetsController";
-    private static final int ANIMATION_DURATION_SHOW_MS = 275;
-    private static final int ANIMATION_DURATION_HIDE_MS = 340;
+    private static final int ANIMATION_DURATION_MOVE_IN_MS = 275;
+    private static final int ANIMATION_DURATION_MOVE_OUT_MS = 340;
+    private static final int ANIMATION_DURATION_FADE_IN_MS = 500;
+    private static final int ANIMATION_DURATION_FADE_OUT_MS = 1500;
+
+    private static final int ANIMATION_DELAY_DIM_MS = 500;
 
     private static final int ANIMATION_DURATION_SYNC_IME_MS = 285;
     private static final int ANIMATION_DURATION_UNSYNC_IME_MS = 200;
 
     private static final int PENDING_CONTROL_TIMEOUT_MS = 2000;
 
-    public static final Interpolator SYSTEM_BARS_INTERPOLATOR =
+    private static final Interpolator SYSTEM_BARS_INSETS_INTERPOLATOR =
             new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+    private static final Interpolator SYSTEM_BARS_ALPHA_INTERPOLATOR =
+            new PathInterpolator(0.3f, 0f, 1f, 1f);
+    private static final Interpolator SYSTEM_BARS_DIM_INTERPOLATOR = alphaFraction -> {
+        // While playing dim animation, alphaFraction is changed from 1f to 0f. Here changes it to
+        // time-based fraction for computing delay and interpolation.
+        float fraction = 1 - alphaFraction;
+        final float fractionDelay = (float) ANIMATION_DELAY_DIM_MS / ANIMATION_DURATION_FADE_OUT_MS;
+        if (fraction <= fractionDelay) {
+            return 1f;
+        } else {
+            float innerFraction = (fraction - fractionDelay) / (1f - fractionDelay);
+            return 1f - SYSTEM_BARS_ALPHA_INTERPOLATOR.getInterpolation(innerFraction);
+        }
+    };
     private static final Interpolator SYNC_IME_INTERPOLATOR =
             new PathInterpolator(0.2f, 0f, 0f, 1f);
     private static final Interpolator LINEAR_OUT_SLOW_IN_INTERPOLATOR =
@@ -206,6 +224,9 @@
     private static final Interpolator FAST_OUT_LINEAR_IN_INTERPOLATOR =
             new PathInterpolator(0.4f, 0f, 1f, 1f);
 
+    /** The amount IME will move up/down when animating in floating mode. */
+    private static final int FLOATING_IME_BOTTOM_INSET_DP = -80;
+
     static final boolean DEBUG = false;
     static final boolean WARN = false;
 
@@ -278,14 +299,12 @@
     public static class InternalAnimationControlListener
             implements WindowInsetsAnimationControlListener {
 
-        /** The amount IME will move up/down when animating in floating mode. */
-        protected static final int FLOATING_IME_BOTTOM_INSET = -80;
-
         private WindowInsetsAnimationController mController;
         private ValueAnimator mAnimator;
         private final boolean mShow;
         private final boolean mHasAnimationCallbacks;
         private final @InsetsType int mRequestedTypes;
+        private final @Behavior int mBehavior;
         private final long mDurationMs;
         private final boolean mDisable;
         private final int mFloatingImeBottomInset;
@@ -301,10 +320,12 @@
         };
 
         public InternalAnimationControlListener(boolean show, boolean hasAnimationCallbacks,
-                int requestedTypes, boolean disable, int floatingImeBottomInset) {
+                @InsetsType int requestedTypes, @Behavior int behavior, boolean disable,
+                int floatingImeBottomInset) {
             mShow = show;
             mHasAnimationCallbacks = hasAnimationCallbacks;
             mRequestedTypes = requestedTypes;
+            mBehavior = behavior;
             mDurationMs = calculateDurationMs();
             mDisable = disable;
             mFloatingImeBottomInset = floatingImeBottomInset;
@@ -335,7 +356,7 @@
             Insets end = mShow
                     ? controller.getShownStateInsets()
                     : hiddenInsets;
-            Interpolator insetsInterpolator = getInterpolator();
+            Interpolator insetsInterpolator = getInsetsInterpolator();
             Interpolator alphaInterpolator = getAlphaInterpolator();
             mAnimator.addUpdateListener(animation -> {
                 float rawFraction = animation.getAnimatedFraction();
@@ -379,7 +400,7 @@
                     + mRequestedTypes);
         }
 
-        Interpolator getInterpolator() {
+        protected Interpolator getInsetsInterpolator() {
             if ((mRequestedTypes & ime()) != 0) {
                 if (mHasAnimationCallbacks) {
                     return SYNC_IME_INTERPOLATOR;
@@ -389,7 +410,12 @@
                     return FAST_OUT_LINEAR_IN_INTERPOLATOR;
                 }
             } else {
-                return SYSTEM_BARS_INTERPOLATOR;
+                if (mBehavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) {
+                    return SYSTEM_BARS_INSETS_INTERPOLATOR;
+                } else {
+                    // Makes insets stay at the shown position.
+                    return input -> mShow ? 1f : 0f;
+                }
             }
         }
 
@@ -405,7 +431,15 @@
                     return FAST_OUT_LINEAR_IN_INTERPOLATOR;
                 }
             } else {
-                return input -> 1f;
+                if (mBehavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) {
+                    return input -> 1f;
+                } else {
+                    if (mShow) {
+                        return SYSTEM_BARS_ALPHA_INTERPOLATOR;
+                    } else {
+                        return SYSTEM_BARS_DIM_INTERPOLATOR;
+                    }
+                }
             }
         }
 
@@ -429,7 +463,11 @@
                     return ANIMATION_DURATION_UNSYNC_IME_MS;
                 }
             } else {
-                return mShow ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS;
+                if (mBehavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) {
+                    return mShow ? ANIMATION_DURATION_MOVE_IN_MS : ANIMATION_DURATION_MOVE_OUT_MS;
+                } else {
+                    return mShow ? ANIMATION_DURATION_FADE_IN_MS : ANIMATION_DURATION_FADE_OUT_MS;
+                }
             }
         }
     }
@@ -882,7 +920,8 @@
         hide(types, false /* fromIme */);
     }
 
-    void hide(@InsetsType int types, boolean fromIme) {
+    @VisibleForTesting
+    public void hide(@InsetsType int types, boolean fromIme) {
         if (fromIme) {
             ImeTracing.getInstance().triggerClientDump("InsetsController#hide",
                     mHost.getInputMethodManager(), null /* icProto */);
@@ -1274,19 +1313,6 @@
         getSourceConsumer(ITYPE_IME).onWindowFocusLost();
     }
 
-    /**
-     * Used by {@link ImeInsetsSourceConsumer} when IME decides to be shown/hidden.
-     * @hide
-     */
-    @VisibleForTesting
-    public void applyImeVisibility(boolean setVisible) {
-        if (setVisible) {
-            show(Type.IME, true /* fromIme */);
-        } else {
-            hide(Type.IME);
-        }
-    }
-
     @VisibleForTesting
     public @AnimationType int getAnimationType(@InternalInsetsType int type) {
         for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
@@ -1358,14 +1384,14 @@
 
         boolean hasAnimationCallbacks = mHost.hasAnimationCallbacks();
         final InternalAnimationControlListener listener = new InternalAnimationControlListener(
-                show, hasAnimationCallbacks, types, skipAnim || mAnimationsDisabled,
-                mHost.dipToPx(InternalAnimationControlListener.FLOATING_IME_BOTTOM_INSET));
+                show, hasAnimationCallbacks, types, mHost.getSystemBarsBehavior(),
+                skipAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP));
 
         // We are about to playing the default animation (show/hide). Passing a null frame indicates
         // the controlled types should be animated regardless of the frame.
         controlAnimationUnchecked(
                 types, null /* cancellationSignal */, listener, null /* frame */, fromIme,
-                listener.getDurationMs(), listener.getInterpolator(),
+                listener.getDurationMs(), listener.getInsetsInterpolator(),
                 show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
                 show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
                 !hasAnimationCallbacks /* useInsetsAnimationThread */);
diff --git a/core/java/android/view/NotificationTopLineView.java b/core/java/android/view/NotificationTopLineView.java
index 05636de..bd20f5b 100644
--- a/core/java/android/view/NotificationTopLineView.java
+++ b/core/java/android/view/NotificationTopLineView.java
@@ -26,6 +26,9 @@
 
 import com.android.internal.R;
 
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * The top line of content in a notification view.
  * This includes the text views and badges but excludes the icon and the expander.
@@ -34,17 +37,23 @@
  */
 @RemoteViews.RemoteView
 public class NotificationTopLineView extends ViewGroup {
+    private final OverflowAdjuster mOverflowAdjuster = new OverflowAdjuster();
     private final int mGravityY;
     private final int mChildMinWidth;
+    private final int mChildHideWidth;
     @Nullable private View mAppName;
     @Nullable private View mTitle;
     private View mHeaderText;
+    private View mHeaderTextDivider;
     private View mSecondaryHeaderText;
+    private View mSecondaryHeaderTextDivider;
     private OnClickListener mFeedbackListener;
     private HeaderTouchListener mTouchListener = new HeaderTouchListener();
     private View mFeedbackIcon;
     private int mHeaderTextMarginEnd;
 
+    private Set<View> mViewsToDisappear = new HashSet<>();
+
     private int mMaxAscent;
     private int mMaxDescent;
 
@@ -66,6 +75,7 @@
         super(context, attrs, defStyleAttr, defStyleRes);
         Resources res = getResources();
         mChildMinWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_min_width);
+        mChildHideWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_hide_width);
 
         // NOTE: Implementation only supports TOP, BOTTOM, and CENTER_VERTICAL gravities,
         // with CENTER_VERTICAL being the default.
@@ -88,7 +98,9 @@
         mAppName = findViewById(R.id.app_name_text);
         mTitle = findViewById(R.id.title);
         mHeaderText = findViewById(R.id.header_text);
+        mHeaderTextDivider = findViewById(R.id.header_text_divider);
         mSecondaryHeaderText = findViewById(R.id.header_text_secondary);
+        mSecondaryHeaderTextDivider = findViewById(R.id.header_text_secondary_divider);
         mFeedbackIcon = findViewById(R.id.feedback);
     }
 
@@ -125,48 +137,37 @@
             maxChildHeight = Math.max(maxChildHeight, childHeight);
         }
 
+        mViewsToDisappear.clear();
         // Ensure that there is at least enough space for the icons
         int endMargin = Math.max(mHeaderTextMarginEnd, getPaddingEnd());
         if (totalWidth > givenWidth - endMargin) {
             int overFlow = totalWidth - givenWidth + endMargin;
 
-            // First shrink the app name, down to a minimum size
-            overFlow = shrinkViewForOverflow(heightSpec, overFlow, mAppName, mChildMinWidth);
-
-            // Next, shrink the header text (this usually has subText)
-            //   This shrinks the subtext first, but not all the way (yet!)
-            overFlow = shrinkViewForOverflow(heightSpec, overFlow, mHeaderText, mChildMinWidth);
-
-            // Next, shrink the secondary header text  (this rarely has conversationTitle)
-            overFlow = shrinkViewForOverflow(heightSpec, overFlow, mSecondaryHeaderText, 0);
-
-            // Next, shrink the title text (this has contentTitle; only in headerless views)
-            overFlow = shrinkViewForOverflow(heightSpec, overFlow, mTitle, mChildMinWidth);
-
-            // Finally, if there is still overflow, shrink the header down to 0 if still necessary.
-            shrinkViewForOverflow(heightSpec, overFlow, mHeaderText, 0);
+            mOverflowAdjuster.resetForOverflow(overFlow, heightSpec)
+                    // First shrink the app name, down to a minimum size
+                    .adjust(mAppName, null, mChildMinWidth)
+                    // Next, shrink the header text (this usually has subText)
+                    //   This shrinks the subtext first, but not all the way (yet!)
+                    .adjust(mHeaderText, mHeaderTextDivider, mChildMinWidth)
+                    // Next, shrink the secondary header text  (this rarely has conversationTitle)
+                    .adjust(mSecondaryHeaderText, mSecondaryHeaderTextDivider, 0)
+                    // Next, shrink the title text (this has contentTitle; only in headerless views)
+                    .adjust(mTitle, null, mChildMinWidth)
+                    // Next, shrink the header down to 0 if still necessary.
+                    .adjust(mHeaderText, mHeaderTextDivider, 0)
+                    // Finally, shrink the title to 0 if necessary (media is super cramped)
+                    .adjust(mTitle, null, 0)
+                    // Clean up
+                    .finish();
         }
         setMeasuredDimension(givenWidth, wrapHeight ? maxChildHeight : givenHeight);
     }
 
-    private int shrinkViewForOverflow(int heightSpec, int overFlow, View targetView,
-            int minimumWidth) {
-        if (targetView != null) {
-            final int oldWidth = targetView.getMeasuredWidth();
-            if (overFlow > 0 && targetView.getVisibility() != GONE && oldWidth > minimumWidth) {
-                // we're still too big
-                int newSize = Math.max(minimumWidth, oldWidth - overFlow);
-                int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
-                targetView.measure(childWidthSpec, heightSpec);
-                overFlow -= oldWidth - newSize;
-            }
-        }
-        return overFlow;
-    }
-
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        int left = getPaddingStart();
+        final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+        final int width = getWidth();
+        int start = getPaddingStart();
         int childCount = getChildCount();
         int ownHeight = b - t;
         int childSpace = ownHeight - mPaddingTop - mPaddingBottom;
@@ -182,8 +183,6 @@
             }
             int childHeight = child.getMeasuredHeight();
             MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
-            int layoutLeft;
-            int layoutRight;
 
             // Calculate vertical alignment of the views, accounting for the view baselines
             int childTop;
@@ -219,19 +218,16 @@
                 default:
                     childTop = mPaddingTop;
             }
-
-            left += params.getMarginStart();
-            int right = left + child.getMeasuredWidth();
-            layoutLeft = left;
-            layoutRight = right;
-            left = right + params.getMarginEnd();
-
-            if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
-                int ltrLeft = layoutLeft;
-                layoutLeft = getWidth() - layoutRight;
-                layoutRight = getWidth() - ltrLeft;
+            if (mViewsToDisappear.contains(child)) {
+                child.layout(start, childTop, start, childTop + childHeight);
+            } else {
+                start += params.getMarginStart();
+                int end = start + child.getMeasuredWidth();
+                int layoutLeft = isRtl ? width - end : start;
+                int layoutRight = isRtl ? width - start : end;
+                start = end + params.getMarginEnd();
+                child.layout(layoutLeft, childTop, layoutRight, childTop + childHeight);
             }
-            child.layout(layoutLeft, childTop, layoutRight, childTop + childHeight);
         }
         updateTouchListener();
     }
@@ -400,4 +396,83 @@
         }
         return mTouchListener.onTouchUp(upX, upY, downX, downY);
     }
+
+    private final class OverflowAdjuster {
+        private int mOverflow;
+        private int mHeightSpec;
+        private View mRegrowView;
+
+        OverflowAdjuster resetForOverflow(int overflow, int heightSpec) {
+            mOverflow = overflow;
+            mHeightSpec = heightSpec;
+            mRegrowView = null;
+            return this;
+        }
+
+        /**
+         * Shrink the targetView's width by up to overFlow, down to minimumWidth.
+         * @param targetView the view to shrink the width of
+         * @param targetDivider a divider view which should be set to 0 width if the targetView is
+         * @param minimumWidth the minimum width allowed for the targetView
+         * @return this object
+         */
+        OverflowAdjuster adjust(View targetView, View targetDivider, int minimumWidth) {
+            if (mOverflow <= 0 || targetView == null || targetView.getVisibility() == View.GONE) {
+                return this;
+            }
+            final int oldWidth = targetView.getMeasuredWidth();
+            if (oldWidth <= minimumWidth) {
+                return this;
+            }
+            // we're too big
+            int newSize = Math.max(minimumWidth, oldWidth - mOverflow);
+            if (minimumWidth == 0 && newSize < mChildHideWidth
+                    && mRegrowView != null && mRegrowView != targetView) {
+                // View is so small it's better to hide it entirely (and its divider and margins)
+                // so we can give that space back to another previously shrunken view.
+                newSize = 0;
+            }
+
+            int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
+            targetView.measure(childWidthSpec, mHeightSpec);
+            mOverflow -= oldWidth - newSize;
+
+            if (newSize == 0) {
+                mViewsToDisappear.add(targetView);
+                mOverflow -= getHorizontalMargins(targetView);
+                if (targetDivider != null && targetDivider.getVisibility() != GONE) {
+                    mViewsToDisappear.add(targetDivider);
+                    int oldDividerWidth = targetDivider.getMeasuredWidth();
+                    int dividerWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.AT_MOST);
+                    targetDivider.measure(dividerWidthSpec, mHeightSpec);
+                    mOverflow -= (oldDividerWidth + getHorizontalMargins(targetDivider));
+                }
+            }
+            if (mOverflow < 0 && mRegrowView != null) {
+                // We're now under-flowing, so regrow the last view.
+                final int regrowCurrentSize = mRegrowView.getMeasuredWidth();
+                final int maxSize = regrowCurrentSize - mOverflow;
+                int regrowWidthSpec = MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.AT_MOST);
+                mRegrowView.measure(regrowWidthSpec, mHeightSpec);
+                finish();
+                return this;
+            }
+
+            if (newSize != 0) {
+                // if we shrunk this view (but did not completely hide it) store it for potential
+                // re-growth if we proactively shorten a future view.
+                mRegrowView = targetView;
+            }
+            return this;
+        }
+
+        void finish() {
+            resetForOverflow(0, 0);
+        }
+
+        private int getHorizontalMargins(View view) {
+            MarginLayoutParams params = (MarginLayoutParams) view.getLayoutParams();
+            return params.getMarginStart() + params.getMarginEnd();
+        }
+    }
 }
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 3ffe0c6..f1eef9f 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1296,7 +1296,7 @@
             mBlastBufferQueue.destroy();
         }
         mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth,
-                mSurfaceHeight, mFormat, true /* TODO */);
+                mSurfaceHeight, mFormat);
     }
 
     private void onDrawFinished() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e4fb611..dbccf10 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -330,7 +330,6 @@
 
     private boolean mUseBLASTAdapter;
     private boolean mForceDisableBLAST;
-    private boolean mEnableTripleBuffering;
 
     private boolean mFastScrollSoundEffectsEnabled;
 
@@ -474,6 +473,7 @@
         FrameInfo frameInfo = mChoreographer.mFrameInfo;
         mViewFrameInfo.populateFrameInfo(frameInfo);
         mViewFrameInfo.reset();
+        mInputEventAssigner.notifyFrameProcessed();
         return frameInfo;
     }
 
@@ -1179,9 +1179,6 @@
                 if ((res & WindowManagerGlobal.ADD_FLAG_USE_BLAST) != 0) {
                     mUseBLASTAdapter = true;
                 }
-                if ((res & WindowManagerGlobal.ADD_FLAG_USE_TRIPLE_BUFFERING) != 0) {
-                    mEnableTripleBuffering = true;
-                }
 
                 if (view instanceof RootViewSurfaceTaker) {
                     mInputQueueCallback =
@@ -1373,17 +1370,10 @@
             // can be used by code on the system process to escape that and enable
             // HW accelerated drawing.  (This is basically for the lock screen.)
 
-            final boolean fakeHwAccelerated = (attrs.privateFlags &
-                    WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED) != 0;
             final boolean forceHwAccelerated = (attrs.privateFlags &
                     WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED) != 0;
 
-            if (fakeHwAccelerated) {
-                // This is exclusively for the preview windows the window manager
-                // shows for launching applications, so they will look more like
-                // the app being launched.
-                mAttachInfo.mHardwareAccelerationRequested = true;
-            } else if (ThreadedRenderer.sRendererEnabled || forceHwAccelerated) {
+            if (ThreadedRenderer.sRendererEnabled || forceHwAccelerated) {
                 if (mAttachInfo.mThreadedRenderer != null) {
                     mAttachInfo.mThreadedRenderer.destroy();
                 }
@@ -1907,7 +1897,7 @@
         Surface ret = null;
         if (mBlastBufferQueue == null) {
             mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl, width, height,
-                    format, mEnableTripleBuffering);
+                    format);
             // We only return the Surface the first time, as otherwise
             // it hasn't changed and there is no need to update.
             ret = mBlastBufferQueue.createSurface();
@@ -8502,11 +8492,6 @@
             consumedBatches = false;
         }
         doProcessInputEvents();
-        if (consumedBatches) {
-            // Must be done after we processed the input events, to mark the completion of the frame
-            // from the input point of view
-            mInputEventAssigner.onChoreographerCallback();
-        }
         return consumedBatches;
     }
 
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 04512c9..9df87dc 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2218,26 +2218,6 @@
         public int flags;
 
         /**
-         * If the window has requested hardware acceleration, but this is not
-         * allowed in the process it is in, then still render it as if it is
-         * hardware accelerated.  This is used for the starting preview windows
-         * in the system process, which don't need to have the overhead of
-         * hardware acceleration (they are just a static rendering), but should
-         * be rendered as such to match the actual window of the app even if it
-         * is hardware accelerated.
-         * Even if the window isn't hardware accelerated, still do its rendering
-         * as if it was.
-         * Like {@link #FLAG_HARDWARE_ACCELERATED} except for trusted system windows
-         * that need hardware acceleration (e.g. LockScreen), where hardware acceleration
-         * is generally disabled. This flag must be specified in addition to
-         * {@link #FLAG_HARDWARE_ACCELERATED} to enable hardware acceleration for system
-         * windows.
-         *
-         * @hide
-         */
-        public static final int PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED = 0x00000001;
-
-        /**
          * In the system process, we globally do not use hardware acceleration
          * because there are many threads doing UI there and they conflict.
          * If certain parts of the UI that really do want to use hardware
@@ -2463,7 +2443,6 @@
          * @hide
          */
         @IntDef(flag = true, prefix="PRIVATE_FLAG_", value = {
-                PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED,
                 PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
                 PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS,
                 SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
@@ -2499,10 +2478,6 @@
         @UnsupportedAppUsage
         @ViewDebug.ExportedProperty(flagMapping = {
                 @ViewDebug.FlagToString(
-                        mask = PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED,
-                        equals = PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED,
-                        name = "FAKE_HARDWARE_ACCELERATED"),
-                @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
                         equals = PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
                         name = "FORCE_HARDWARE_ACCELERATED"),
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 47ac1ee..18013e8 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -118,7 +118,6 @@
 
     public static final int ADD_FLAG_IN_TOUCH_MODE = 0x1;
     public static final int ADD_FLAG_APP_VISIBLE = 0x2;
-    public static final int ADD_FLAG_USE_TRIPLE_BUFFERING = 0x4;
     public static final int ADD_FLAG_USE_BLAST = 0x8;
 
     /**
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index 37220fe..c5bce28 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -608,7 +608,12 @@
         Preconditions.checkArgumentNonnegative(afterLength);
 
         final Editable content = getEditable();
-        if (content == null) return null;
+        // If {@link #getEditable()} is null or {@code mEditable} is equal to {@link #getEditable()}
+        // (a.k.a, a fake editable), it means we cannot get valid content from the editable, so
+        // fallback to retrieve surrounding text from other APIs.
+        if (content == null || mEditable == content) {
+            return InputConnection.super.getSurroundingText(beforeLength, afterLength, flags);
+        }
 
         int selStart = Selection.getSelectionStart(content);
         int selEnd = Selection.getSelectionEnd(content);
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 34a60bb..38019c9 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -325,16 +325,16 @@
 
         CharSequence textBeforeCursor = getTextBeforeCursor(beforeLength, flags);
         if (textBeforeCursor == null) {
-            textBeforeCursor = "";
+            return null;
+        }
+        CharSequence textAfterCursor = getTextAfterCursor(afterLength, flags);
+        if (textAfterCursor == null) {
+            return null;
         }
         CharSequence selectedText = getSelectedText(flags);
         if (selectedText == null) {
             selectedText = "";
         }
-        CharSequence textAfterCursor = getTextAfterCursor(afterLength, flags);
-        if (textAfterCursor == null) {
-            textAfterCursor = "";
-        }
         CharSequence surroundingText =
                 TextUtils.concat(textBeforeCursor, selectedText, textAfterCursor);
         return new SurroundingText(surroundingText, textBeforeCursor.length(),
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 9872dc0..3cd3902 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -511,7 +511,6 @@
     static final int MSG_TIMEOUT_INPUT_EVENT = 6;
     static final int MSG_FLUSH_INPUT_EVENT = 7;
     static final int MSG_REPORT_FULLSCREEN_MODE = 10;
-    static final int MSG_APPLY_IME_VISIBILITY = 20;
     static final int MSG_UPDATE_ACTIVITY_VIEW_TO_SCREEN_MATRIX = 30;
 
     private static boolean isAutofillUIShowing(View servedView) {
@@ -985,17 +984,6 @@
                     }
                     return;
                 }
-                case MSG_APPLY_IME_VISIBILITY: {
-                    synchronized (mH) {
-                        if (mImeInsetsConsumer != null) {
-                            ImeTracing.getInstance().triggerClientDump(
-                                    "ImeInsetsSourceConsumer#applyImeVisibility",
-                                    InputMethodManager.this, null /* icProto */);
-                            mImeInsetsConsumer.applyImeVisibility(msg.arg1 != 0);
-                        }
-                    }
-                    return;
-                }
                 case MSG_UPDATE_ACTIVITY_VIEW_TO_SCREEN_MATRIX: {
                     final float[] matrixValues = (float[]) msg.obj;
                     final int bindSequence = msg.arg1;
@@ -1090,12 +1078,6 @@
         }
 
         @Override
-        public void applyImeVisibility(boolean setVisible) {
-            mH.obtainMessage(MSG_APPLY_IME_VISIBILITY, setVisible ? 1 : 0, 0)
-                    .sendToTarget();
-        }
-
-        @Override
         public void updateActivityViewToScreenMatrix(int bindSequence, float[] matrixValues) {
             mH.obtainMessage(MSG_UPDATE_ACTIVITY_VIEW_TO_SCREEN_MATRIX, bindSequence, 0,
                     matrixValues).sendToTarget();
diff --git a/core/java/android/view/translation/ITranslationManager.aidl b/core/java/android/view/translation/ITranslationManager.aidl
index d347f31..dbc32e9 100644
--- a/core/java/android/view/translation/ITranslationManager.aidl
+++ b/core/java/android/view/translation/ITranslationManager.aidl
@@ -18,7 +18,9 @@
 
 import android.os.IBinder;
 import android.os.IRemoteCallback;
+import android.os.ResultReceiver;
 import android.view.autofill.AutofillId;
+import android.view.translation.TranslationContext;
 import android.view.translation.TranslationSpec;
 import com.android.internal.os.IResultReceiver;
 
@@ -30,17 +32,17 @@
  * {@hide}
  */
 oneway interface ITranslationManager {
-    void getSupportedLocales(in IResultReceiver receiver, int userId);
-    void onSessionCreated(in TranslationSpec sourceSpec, in TranslationSpec destSpec,
+    void onTranslationCapabilitiesRequest(int sourceFormat, int destFormat,
+         in ResultReceiver receiver, int userId);
+    void onSessionCreated(in TranslationContext translationContext,
          int sessionId, in IResultReceiver receiver, int userId);
 
     void updateUiTranslationState(int state, in TranslationSpec sourceSpec,
-         in TranslationSpec destSpec, in List<AutofillId> viewIds, IBinder token, int taskId,
+         in TranslationSpec targetSpec, in List<AutofillId> viewIds, IBinder token, int taskId,
          int userId);
     // deprecated
     void updateUiTranslationStateByTaskId(int state, in TranslationSpec sourceSpec,
-         in TranslationSpec destSpec, in List<AutofillId> viewIds, int taskId,
-         int userId);
+         in TranslationSpec targetSpec, in List<AutofillId> viewIds, int taskId, int userId);
 
     void registerUiTranslationStateCallback(in IRemoteCallback callback, int userId);
     void unregisterUiTranslationStateCallback(in IRemoteCallback callback, int userId);
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java b/core/java/android/view/translation/TranslationCapability.aidl
similarity index 61%
copy from services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
copy to core/java/android/view/translation/TranslationCapability.aidl
index cab5ad2..3a80b17 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
+++ b/core/java/android/view/translation/TranslationCapability.aidl
@@ -14,17 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.server.timezonedetector.location;
+package android.view.translation;
 
-import android.annotation.NonNull;
-
-import com.android.i18n.timezone.ZoneInfoDb;
-
-class ZoneInfoDbTimeZoneIdValidator implements
-        LocationTimeZoneProvider.TimeZoneIdValidator {
-
-    @Override
-    public boolean isValid(@NonNull String timeZoneId) {
-        return ZoneInfoDb.getInstance().hasTimeZone(timeZoneId);
-    }
-}
+parcelable TranslationCapability;
diff --git a/core/java/android/view/translation/TranslationCapability.java b/core/java/android/view/translation/TranslationCapability.java
new file mode 100644
index 0000000..28b2113
--- /dev/null
+++ b/core/java/android/view/translation/TranslationCapability.java
@@ -0,0 +1,289 @@
+/*
+ * 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.view.translation;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Capability class holding information for a pair of {@link TranslationSpec}s.
+ *
+ * <p>Holds information and limitations on how to create a {@link TranslationContext} which can
+ * be used by {@link TranslationManager#createTranslator(TranslationContext)}.
+ */
+@DataClass(genHiddenConstDefs = true, genToString = true, genConstructor = false)
+public final class TranslationCapability implements Parcelable {
+
+    /**
+     * TODO: fill in javadoc
+     */
+    public static final @ModelState int STATE_AVAILABLE_TO_DOWNLOAD = 1;
+    /**
+     * TODO: fill in javadoc
+     */
+    public static final @ModelState int STATE_DOWNLOADING = 2;
+    /**
+     * TODO: fill in javadoc
+     */
+    public static final @ModelState int STATE_ON_DEVICE = 3;
+
+    /**
+     * The state of translation readiness between {@code mSourceSpec} and {@code mTargetSpec}.
+     */
+    private final @ModelState int mState;
+
+    /**
+     * {@link TranslationSpec} describing the source data specs for this
+     * capability.
+     */
+    @NonNull
+    private final TranslationSpec mSourceSpec;
+
+    /**
+     * {@link TranslationSpec} describing the target data specs for this
+     * capability.
+     */
+    @NonNull
+    private final TranslationSpec mTargetSpec;
+
+    /**
+     * Whether ui translation for the source-target {@link TranslationSpec}s is enabled.
+     *
+     * <p>Translation service will still support translation requests for this capability.</p>
+     */
+    private final boolean mUiTranslationEnabled;
+
+    /**
+     * Translation flags for settings that are supported by the
+     * {@link android.service.translation.TranslationService} between the {@link TranslationSpec}s
+     * provided in this capability.
+     */
+    private final @TranslationContext.TranslationFlag int mSupportedTranslationFlags;
+
+    /**
+     * Constructor for creating a {@link TranslationCapability}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public TranslationCapability(@ModelState int state, @NonNull TranslationSpec sourceSpec,
+            @NonNull TranslationSpec targetSpec, boolean uiTranslationEnabled,
+            @TranslationContext.TranslationFlag int supportedTranslationFlags) {
+        Objects.requireNonNull(sourceSpec, "sourceSpec should not be null");
+        Objects.requireNonNull(targetSpec, "targetSpec should not be null");
+
+        this.mState = state;
+        this.mSourceSpec = sourceSpec;
+        this.mTargetSpec = targetSpec;
+        this.mUiTranslationEnabled = uiTranslationEnabled;
+        this.mSupportedTranslationFlags = supportedTranslationFlags;
+    }
+
+
+
+    // Code below generated by codegen v1.0.22.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/TranslationCapability.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /** @hide */
+    @IntDef(prefix = "STATE_", value = {
+        STATE_AVAILABLE_TO_DOWNLOAD,
+        STATE_DOWNLOADING,
+        STATE_ON_DEVICE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface ModelState {}
+
+    /** @hide */
+    @DataClass.Generated.Member
+    public static String modelStateToString(@ModelState int value) {
+        switch (value) {
+            case STATE_AVAILABLE_TO_DOWNLOAD:
+                    return "STATE_AVAILABLE_TO_DOWNLOAD";
+            case STATE_DOWNLOADING:
+                    return "STATE_DOWNLOADING";
+            case STATE_ON_DEVICE:
+                    return "STATE_ON_DEVICE";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    /**
+     * The state of translation readiness between {@code mSourceSpec} and {@code mTargetSpec}.
+     */
+    @DataClass.Generated.Member
+    public @ModelState int getState() {
+        return mState;
+    }
+
+    /**
+     * {@link TranslationSpec} describing the source data specs for this
+     * capability.
+     */
+    @DataClass.Generated.Member
+    public @NonNull TranslationSpec getSourceSpec() {
+        return mSourceSpec;
+    }
+
+    /**
+     * {@link TranslationSpec} describing the target data specs for this
+     * capability.
+     */
+    @DataClass.Generated.Member
+    public @NonNull TranslationSpec getTargetSpec() {
+        return mTargetSpec;
+    }
+
+    /**
+     * Whether ui translation for the source-target {@link TranslationSpec}s is enabled.
+     *
+     * <p>Translation service will still support translation requests for this capability.</p>
+     */
+    @DataClass.Generated.Member
+    public boolean isUiTranslationEnabled() {
+        return mUiTranslationEnabled;
+    }
+
+    /**
+     * Translation flags for settings that are supported by the
+     * {@link android.service.translation.TranslationService} between the {@link TranslationSpec}s
+     * provided in this capability.
+     */
+    @DataClass.Generated.Member
+    public @TranslationContext.TranslationFlag int getSupportedTranslationFlags() {
+        return mSupportedTranslationFlags;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "TranslationCapability { " +
+                "state = " + modelStateToString(mState) + ", " +
+                "sourceSpec = " + mSourceSpec + ", " +
+                "targetSpec = " + mTargetSpec + ", " +
+                "uiTranslationEnabled = " + mUiTranslationEnabled + ", " +
+                "supportedTranslationFlags = " + mSupportedTranslationFlags +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mUiTranslationEnabled) flg |= 0x8;
+        dest.writeByte(flg);
+        dest.writeInt(mState);
+        dest.writeTypedObject(mSourceSpec, flags);
+        dest.writeTypedObject(mTargetSpec, flags);
+        dest.writeInt(mSupportedTranslationFlags);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ TranslationCapability(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        boolean uiTranslationEnabled = (flg & 0x8) != 0;
+        int state = in.readInt();
+        TranslationSpec sourceSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+        TranslationSpec targetSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+        int supportedTranslationFlags = in.readInt();
+
+        this.mState = state;
+
+        if (!(mState == STATE_AVAILABLE_TO_DOWNLOAD)
+                && !(mState == STATE_DOWNLOADING)
+                && !(mState == STATE_ON_DEVICE)) {
+            throw new java.lang.IllegalArgumentException(
+                    "state was " + mState + " but must be one of: "
+                            + "STATE_AVAILABLE_TO_DOWNLOAD(" + STATE_AVAILABLE_TO_DOWNLOAD + "), "
+                            + "STATE_DOWNLOADING(" + STATE_DOWNLOADING + "), "
+                            + "STATE_ON_DEVICE(" + STATE_ON_DEVICE + ")");
+        }
+
+        this.mSourceSpec = sourceSpec;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mSourceSpec);
+        this.mTargetSpec = targetSpec;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mTargetSpec);
+        this.mUiTranslationEnabled = uiTranslationEnabled;
+        this.mSupportedTranslationFlags = supportedTranslationFlags;
+        com.android.internal.util.AnnotationValidations.validate(
+                TranslationContext.TranslationFlag.class, null, mSupportedTranslationFlags);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<TranslationCapability> CREATOR
+            = new Parcelable.Creator<TranslationCapability>() {
+        @Override
+        public TranslationCapability[] newArray(int size) {
+            return new TranslationCapability[size];
+        }
+
+        @Override
+        public TranslationCapability createFromParcel(@NonNull android.os.Parcel in) {
+            return new TranslationCapability(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1616438309593L,
+            codegenVersion = "1.0.22",
+            sourceFile = "frameworks/base/core/java/android/view/translation/TranslationCapability.java",
+            inputSignatures = "public static final @android.view.translation.TranslationCapability.ModelState int STATE_AVAILABLE_TO_DOWNLOAD\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_DOWNLOADING\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_ON_DEVICE\nprivate final @android.view.translation.TranslationCapability.ModelState int mState\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mSourceSpec\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mTargetSpec\nprivate final  boolean mUiTranslationEnabled\nprivate final @android.view.translation.TranslationContext.TranslationFlag int mSupportedTranslationFlags\nclass TranslationCapability extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstDefs=true, genToString=true, genConstructor=false)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java b/core/java/android/view/translation/TranslationContext.aidl
similarity index 61%
copy from services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
copy to core/java/android/view/translation/TranslationContext.aidl
index cab5ad2..cb6c23f 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
+++ b/core/java/android/view/translation/TranslationContext.aidl
@@ -14,17 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.server.timezonedetector.location;
+package android.view.translation;
 
-import android.annotation.NonNull;
-
-import com.android.i18n.timezone.ZoneInfoDb;
-
-class ZoneInfoDbTimeZoneIdValidator implements
-        LocationTimeZoneProvider.TimeZoneIdValidator {
-
-    @Override
-    public boolean isValid(@NonNull String timeZoneId) {
-        return ZoneInfoDb.getInstance().hasTimeZone(timeZoneId);
-    }
-}
+parcelable TranslationContext;
diff --git a/core/java/android/view/translation/TranslationContext.java b/core/java/android/view/translation/TranslationContext.java
new file mode 100644
index 0000000..1d3d182
--- /dev/null
+++ b/core/java/android/view/translation/TranslationContext.java
@@ -0,0 +1,309 @@
+/*
+ * 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.view.translation;
+
+import android.annotation.NonNull;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Info class holding information for {@link Translator}s and used by
+ * {@link TranslationManager#createTranslator(TranslationContext)}.
+ */
+@DataClass(genHiddenConstDefs = true, genToString = true, genBuilder = true)
+public final class TranslationContext implements Parcelable {
+
+    /**
+     * This context will perform translations in low latency mode.
+     */
+    public static final @TranslationFlag int FLAG_LOW_LATENCY = 0x1;
+    /**
+     * This context will enable the {@link Translator} to return transliteration results.
+     */
+    public static final @TranslationFlag int FLAG_TRANSLITERATION = 0x2;
+    /**
+     * This context will enable the {@link Translator} to return dictionary results.
+     */
+    public static final @TranslationFlag int FLAG_DICTIONARY_DESCRIPTION = 0x4;
+
+    /**
+     * {@link TranslationSpec} describing the source data to be translated.
+     */
+    @NonNull
+    private final TranslationSpec mSourceSpec;
+
+    /**
+     * {@link TranslationSpec} describing the target translated data.
+     */
+    @NonNull
+    private final TranslationSpec mTargetSpec;
+
+    /**
+     * Translation flags to be used by the {@link Translator}
+     */
+    private final @TranslationFlag int mTranslationFlags;
+
+    private static int defaultTranslationFlags() {
+        return 0;
+    }
+
+    @DataClass.Suppress({"setSourceSpec", "setTargetSpec"})
+    abstract static class BaseBuilder {
+
+    }
+
+
+
+    // Code below generated by codegen v1.0.22.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/TranslationContext.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /** @hide */
+    @android.annotation.IntDef(flag = true, prefix = "FLAG_", value = {
+        FLAG_LOW_LATENCY,
+        FLAG_TRANSLITERATION,
+        FLAG_DICTIONARY_DESCRIPTION
+    })
+    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface TranslationFlag {}
+
+    /** @hide */
+    @DataClass.Generated.Member
+    public static String translationFlagToString(@TranslationFlag int value) {
+        return com.android.internal.util.BitUtils.flagsToString(
+                value, TranslationContext::singleTranslationFlagToString);
+    }
+
+    @DataClass.Generated.Member
+    static String singleTranslationFlagToString(@TranslationFlag int value) {
+        switch (value) {
+            case FLAG_LOW_LATENCY:
+                    return "FLAG_LOW_LATENCY";
+            case FLAG_TRANSLITERATION:
+                    return "FLAG_TRANSLITERATION";
+            case FLAG_DICTIONARY_DESCRIPTION:
+                    return "FLAG_DICTIONARY_DESCRIPTION";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    @DataClass.Generated.Member
+    /* package-private */ TranslationContext(
+            @NonNull TranslationSpec sourceSpec,
+            @NonNull TranslationSpec targetSpec,
+            @TranslationFlag int translationFlags) {
+        this.mSourceSpec = sourceSpec;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mSourceSpec);
+        this.mTargetSpec = targetSpec;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mTargetSpec);
+        this.mTranslationFlags = translationFlags;
+
+        com.android.internal.util.Preconditions.checkFlagsArgument(
+                mTranslationFlags,
+                FLAG_LOW_LATENCY
+                        | FLAG_TRANSLITERATION
+                        | FLAG_DICTIONARY_DESCRIPTION);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * {@link TranslationSpec} describing the source data to be translated.
+     */
+    @DataClass.Generated.Member
+    public @NonNull TranslationSpec getSourceSpec() {
+        return mSourceSpec;
+    }
+
+    /**
+     * {@link TranslationSpec} describing the target translated data.
+     */
+    @DataClass.Generated.Member
+    public @NonNull TranslationSpec getTargetSpec() {
+        return mTargetSpec;
+    }
+
+    /**
+     * Translation flags to be used by the {@link Translator}
+     */
+    @DataClass.Generated.Member
+    public @TranslationFlag int getTranslationFlags() {
+        return mTranslationFlags;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "TranslationContext { " +
+                "sourceSpec = " + mSourceSpec + ", " +
+                "targetSpec = " + mTargetSpec + ", " +
+                "translationFlags = " + translationFlagToString(mTranslationFlags) +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeTypedObject(mSourceSpec, flags);
+        dest.writeTypedObject(mTargetSpec, flags);
+        dest.writeInt(mTranslationFlags);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ TranslationContext(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        TranslationSpec sourceSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+        TranslationSpec targetSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+        int translationFlags = in.readInt();
+
+        this.mSourceSpec = sourceSpec;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mSourceSpec);
+        this.mTargetSpec = targetSpec;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mTargetSpec);
+        this.mTranslationFlags = translationFlags;
+
+        com.android.internal.util.Preconditions.checkFlagsArgument(
+                mTranslationFlags,
+                FLAG_LOW_LATENCY
+                        | FLAG_TRANSLITERATION
+                        | FLAG_DICTIONARY_DESCRIPTION);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<TranslationContext> CREATOR
+            = new Parcelable.Creator<TranslationContext>() {
+        @Override
+        public TranslationContext[] newArray(int size) {
+            return new TranslationContext[size];
+        }
+
+        @Override
+        public TranslationContext createFromParcel(@NonNull android.os.Parcel in) {
+            return new TranslationContext(in);
+        }
+    };
+
+    /**
+     * A builder for {@link TranslationContext}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @DataClass.Generated.Member
+    public static final class Builder extends BaseBuilder {
+
+        private @NonNull TranslationSpec mSourceSpec;
+        private @NonNull TranslationSpec mTargetSpec;
+        private @TranslationFlag int mTranslationFlags;
+
+        private long mBuilderFieldsSet = 0L;
+
+        /**
+         * Creates a new Builder.
+         *
+         * @param sourceSpec
+         *   {@link TranslationSpec} describing the source data to be translated.
+         * @param targetSpec
+         *   {@link TranslationSpec} describing the target translated data.
+         */
+        public Builder(
+                @NonNull TranslationSpec sourceSpec,
+                @NonNull TranslationSpec targetSpec) {
+            mSourceSpec = sourceSpec;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mSourceSpec);
+            mTargetSpec = targetSpec;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mTargetSpec);
+        }
+
+        /**
+         * Translation flags to be used by the {@link Translator}
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setTranslationFlags(@TranslationFlag int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4;
+            mTranslationFlags = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull TranslationContext build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x8; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x4) == 0) {
+                mTranslationFlags = defaultTranslationFlags();
+            }
+            TranslationContext o = new TranslationContext(
+                    mSourceSpec,
+                    mTargetSpec,
+                    mTranslationFlags);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x8) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+
+    @DataClass.Generated(
+            time = 1616199021789L,
+            codegenVersion = "1.0.22",
+            sourceFile = "frameworks/base/core/java/android/view/translation/TranslationContext.java",
+            inputSignatures = "public static final @android.view.translation.TranslationContext.TranslationFlag int FLAG_LOW_LATENCY\npublic static final @android.view.translation.TranslationContext.TranslationFlag int FLAG_TRANSLITERATION\npublic static final @android.view.translation.TranslationContext.TranslationFlag int FLAG_DICTIONARY_DESCRIPTION\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mSourceSpec\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mTargetSpec\nprivate final @android.view.translation.TranslationContext.TranslationFlag int mTranslationFlags\nprivate static  int defaultTranslationFlags()\nclass TranslationContext extends java.lang.Object implements [android.os.Parcelable]\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genHiddenConstDefs=true, genToString=true, genBuilder=true)\nclass BaseBuilder extends java.lang.Object implements []")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/view/translation/TranslationManager.java b/core/java/android/view/translation/TranslationManager.java
index 6554e1a..66b45f3 100644
--- a/core/java/android/view/translation/TranslationManager.java
+++ b/core/java/android/view/translation/TranslationManager.java
@@ -21,24 +21,27 @@
 import android.annotation.RequiresFeature;
 import android.annotation.SystemService;
 import android.annotation.WorkerThread;
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
-import android.service.translation.TranslationService;
+import android.os.SynchronousResultReceiver;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.SyncResultReceiver;
 
+import java.util.ArrayList;
 import java.util.Collections;
-import java.util.List;
 import java.util.Objects;
 import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -55,9 +58,9 @@
     private static final String TAG = "TranslationManager";
 
     /**
-     * Timeout for calls to system_server.
+     * Timeout for calls to system_server, default 1 minute.
      */
-    static final int SYNC_CALLS_TIMEOUT_MS = 5000;
+    static final int SYNC_CALLS_TIMEOUT_MS = 60_000;
     /**
      * The result code from result receiver success.
      * @hide
@@ -69,6 +72,17 @@
      */
     public static final int STATUS_SYNC_CALL_FAIL = 2;
 
+    /**
+     * Name of the extra used to pass the translation capabilities.
+     * @hide
+     */
+    public static final String EXTRA_CAPABILITIES = "translation_capabilities";
+
+    // TODO: implement update listeners and propagate updates.
+    @GuardedBy("mLock")
+    private final ArrayMap<Pair<Integer, Integer>, ArrayList<PendingIntent>>
+            mTranslationCapabilityUpdateListeners = new ArrayMap<>();
+
     private static final Random ID_GENERATOR = new Random();
     private final Object mLock = new Object();
 
@@ -87,7 +101,7 @@
 
     @NonNull
     @GuardedBy("mLock")
-    private final ArrayMap<Pair<TranslationSpec, TranslationSpec>, Integer> mTranslatorIds =
+    private final ArrayMap<TranslationContext, Integer> mTranslatorIds =
             new ArrayMap<>();
 
     @NonNull
@@ -110,23 +124,20 @@
      *
      * <p><strong>NOTE: </strong>Call on a worker thread.
      *
-     * @param sourceSpec {@link TranslationSpec} for the data to be translated.
-     * @param destSpec {@link TranslationSpec} for the translated data.
+     * @param translationContext {@link TranslationContext} containing the specs for creating the
+     *                                                     Translator.
      * @return a {@link Translator} to be used for calling translation APIs.
      */
     @Nullable
     @WorkerThread
-    public Translator createTranslator(@NonNull TranslationSpec sourceSpec,
-            @NonNull TranslationSpec destSpec) {
-        Objects.requireNonNull(sourceSpec, "sourceSpec cannot be null");
-        Objects.requireNonNull(sourceSpec, "destSpec cannot be null");
+    public Translator createTranslator(@NonNull TranslationContext translationContext) {
+        Objects.requireNonNull(translationContext, "translationContext cannot be null");
 
         synchronized (mLock) {
             // TODO(b/176464808): Disallow multiple Translator now, it will throw
             //  IllegalStateException. Need to discuss if we can allow multiple Translators.
-            final Pair<TranslationSpec, TranslationSpec> specs = new Pair<>(sourceSpec, destSpec);
-            if (mTranslatorIds.containsKey(specs)) {
-                return mTranslators.get(mTranslatorIds.get(specs));
+            if (mTranslatorIds.containsKey(translationContext)) {
+                return mTranslators.get(mTranslatorIds.get(translationContext));
             }
 
             int translatorId;
@@ -134,7 +145,7 @@
                 translatorId = Math.abs(ID_GENERATOR.nextInt());
             } while (translatorId == 0 || mTranslators.indexOfKey(translatorId) >= 0);
 
-            final Translator newTranslator = new Translator(mContext, sourceSpec, destSpec,
+            final Translator newTranslator = new Translator(mContext, translationContext,
                     translatorId, this, mHandler, mService);
             // Start the Translator session and wait for the result
             newTranslator.start();
@@ -143,7 +154,7 @@
                     return null;
                 }
                 mTranslators.put(translatorId, newTranslator);
-                mTranslatorIds.put(specs, translatorId);
+                mTranslatorIds.put(translationContext, translatorId);
                 return newTranslator;
             } catch (Translator.ServiceBinderReceiver.TimeoutException e) {
                 // TODO(b/176464808): maybe make SyncResultReceiver.TimeoutException constructor
@@ -155,32 +166,103 @@
     }
 
     /**
-     * Returns a list of locales supported by the {@link TranslationService}.
+     * Returns a set of {@link TranslationCapability}s describing the capabilities for
+     * {@link Translator}s.
+     *
+     * <p>These translation capabilities contains a source and target {@link TranslationSpec}
+     * representing the data expected for both ends of translation process. The capabilities
+     * provides the information and limitations for generating a {@link TranslationContext}.
+     * The context object can then be used by {@link #createTranslator(TranslationContext)} to
+     * obtain a {@link Translator} for translations.</p>
      *
      * <p><strong>NOTE: </strong>Call on a worker thread.
      *
-     * TODO: Change to correct language/locale format
+     * @param sourceFormat data format for the input data to be translated.
+     * @param targetFormat data format for the expected translated output data.
+     * @return A set of {@link TranslationCapability}s.
      */
     @NonNull
     @WorkerThread
-    public List<String> getSupportedLocales() {
+    public Set<TranslationCapability> getTranslationCapabilities(
+            @TranslationSpec.DataFormat int sourceFormat,
+            @TranslationSpec.DataFormat int targetFormat) {
         try {
-            // TODO: implement it
-            final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
-            mService.getSupportedLocales(receiver, mContext.getUserId());
-            int resutCode = receiver.getIntResult();
-            if (resutCode != STATUS_SYNC_CALL_SUCCESS) {
-                return Collections.emptyList();
+            final SynchronousResultReceiver receiver = new SynchronousResultReceiver();
+            mService.onTranslationCapabilitiesRequest(sourceFormat, targetFormat, receiver,
+                    mContext.getUserId());
+            final SynchronousResultReceiver.Result result =
+                    receiver.awaitResult(SYNC_CALLS_TIMEOUT_MS);
+            if (result.resultCode != STATUS_SYNC_CALL_SUCCESS) {
+                return Collections.emptySet();
             }
-            return receiver.getParcelableResult();
+            return new ArraySet<>(
+                    (TranslationCapability[]) result.bundle.getParcelableArray(EXTRA_CAPABILITIES));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
-        } catch (SyncResultReceiver.TimeoutException e) {
-            Log.e(TAG, "Timed out getting supported locales: " + e);
-            return Collections.emptyList();
+        } catch (TimeoutException e) {
+            Log.e(TAG, "Timed out getting supported translation capabilities: " + e);
+            return Collections.emptySet();
         }
     }
 
+    /**
+     * Registers a {@link PendingIntent} to listen for updates on states of
+     * {@link TranslationCapability}s.
+     *
+     * <p>IMPORTANT: the pending intent must be called to start a service, or a broadcast if it is
+     * an explicit intent.</p>
+     *
+     * @param sourceFormat data format for the input data to be translated.
+     * @param targetFormat data format for the expected translated output data.
+     * @param pendingIntent the pending intent to invoke when updates are received.
+     */
+    public void addTranslationCapabilityUpdateListener(
+            @TranslationSpec.DataFormat int sourceFormat,
+            @TranslationSpec.DataFormat int targetFormat,
+            @NonNull PendingIntent pendingIntent) {
+        Objects.requireNonNull(pendingIntent, "pending intent should not be null");
+
+        synchronized (mLock) {
+            final Pair<Integer, Integer> formatPair = new Pair<>(sourceFormat, targetFormat);
+            mTranslationCapabilityUpdateListeners.computeIfAbsent(formatPair,
+                    (formats) -> new ArrayList<>()).add(pendingIntent);
+        }
+    }
+
+    /**
+     * Unregisters a {@link PendingIntent} to listen for updates on states of
+     * {@link TranslationCapability}s.
+     *
+     * @param sourceFormat data format for the input data to be translated.
+     * @param targetFormat data format for the expected translated output data.
+     * @param pendingIntent the pending intent to unregister
+     */
+    public void removeTranslationCapabilityUpdateListener(
+            @TranslationSpec.DataFormat int sourceFormat,
+            @TranslationSpec.DataFormat int targetFormat,
+            @NonNull PendingIntent pendingIntent) {
+        Objects.requireNonNull(pendingIntent, "pending intent should not be null");
+
+        synchronized (mLock) {
+            final Pair<Integer, Integer> formatPair = new Pair<>(sourceFormat, targetFormat);
+            if (mTranslationCapabilityUpdateListeners.containsKey(formatPair)) {
+                final ArrayList<PendingIntent> intents =
+                        mTranslationCapabilityUpdateListeners.get(formatPair);
+                if (intents.contains(pendingIntent)) {
+                    intents.remove(pendingIntent);
+                } else {
+                    Log.w(TAG, "pending intent=" + pendingIntent + " does not exist in "
+                            + "mTranslationCapabilityUpdateListeners");
+                }
+            } else {
+                Log.w(TAG, "format pair=" + formatPair + " does not exist in "
+                        + "mTranslationCapabilityUpdateListeners");
+            }
+        }
+    }
+
+    //TODO: Add method to propagate updates to mTCapabilityUpdateListeners
+
     void removeTranslator(int id) {
         synchronized (mLock) {
             mTranslators.remove(id);
diff --git a/core/java/android/view/translation/Translator.java b/core/java/android/view/translation/Translator.java
index 0cc26e1..6b26e06 100644
--- a/core/java/android/view/translation/Translator.java
+++ b/core/java/android/view/translation/Translator.java
@@ -44,7 +44,7 @@
 import java.util.function.Consumer;
 
 /**
- * The {@link Translator} for translation, defined by a source and a dest {@link TranslationSpec}.
+ * The {@link Translator} for translation, defined by a {@link TranslationContext}.
  */
 @SuppressLint("NotCloseable")
 public class Translator {
@@ -62,10 +62,7 @@
     private final Context mContext;
 
     @NonNull
-    private final TranslationSpec mSourceSpec;
-
-    @NonNull
-    private final TranslationSpec mDestSpec;
+    private final TranslationContext mTranslationContext;
 
     @NonNull
     private final TranslationManager mManager;
@@ -164,13 +161,11 @@
      * @hide
      */
     public Translator(@NonNull Context context,
-            @NonNull TranslationSpec sourceSpec,
-            @NonNull TranslationSpec destSpec, int sessionId,
+            @NonNull TranslationContext translationContext, int sessionId,
             @NonNull TranslationManager translationManager, @NonNull Handler handler,
             @Nullable ITranslationManager systemServerBinder) {
         mContext = context;
-        mSourceSpec = sourceSpec;
-        mDestSpec = destSpec;
+        mTranslationContext = translationContext;
         mId = sessionId;
         mManager = translationManager;
         mHandler = handler;
@@ -183,7 +178,7 @@
      */
     void start() {
         try {
-            mSystemServerBinder.onSessionCreated(mSourceSpec, mDestSpec, mId,
+            mSystemServerBinder.onSessionCreated(mTranslationContext, mId,
                     mServiceBinderReceiver, mContext.getUserId());
         } catch (RemoteException e) {
             Log.w(TAG, "RemoteException calling startSession(): " + e);
@@ -223,8 +218,7 @@
 
     /** @hide */
     public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
-        pw.print(prefix); pw.print("sourceSpec: "); pw.println(mSourceSpec);
-        pw.print(prefix); pw.print("destSpec: "); pw.println(mDestSpec);
+        pw.print(prefix); pw.print("translationContext: "); pw.println(mTranslationContext);
     }
 
     /**
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index 7f934c0..d79ecca 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -376,15 +376,17 @@
     }
 
     private Translator createTranslatorIfNeeded(
-            TranslationSpec sourceSpec, TranslationSpec destSpec) {
+            TranslationSpec sourceSpec, TranslationSpec targetSpec) {
         final TranslationManager tm = mContext.getSystemService(TranslationManager.class);
         if (tm == null) {
             Log.e(TAG, "Can not find TranslationManager when trying to create translator.");
             return null;
         }
-        final Translator translator = tm.createTranslator(sourceSpec, destSpec);
+        final TranslationContext translationContext = new TranslationContext(sourceSpec,
+                targetSpec, /* translationFlags= */ 0);
+        final Translator translator = tm.createTranslator(translationContext);
         if (translator != null) {
-            final Pair<TranslationSpec, TranslationSpec> specs = new Pair<>(sourceSpec, destSpec);
+            final Pair<TranslationSpec, TranslationSpec> specs = new Pair<>(sourceSpec, targetSpec);
             mTranslators.put(specs, translator);
         }
         return translator;
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 58dfc0d..34ad659 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -640,6 +640,12 @@
                     mWidth,
                     mHeight
             );
+        } else {
+            // This is TYPE_STRETCH and drawing into a Canvas that isn't a Recording Canvas,
+            // so no effect can be shown. Just end the effect.
+            mState = STATE_IDLE;
+            mDistance = 0;
+            mVelocity = 0;
         }
 
         boolean oneLastFrame = false;
@@ -771,8 +777,9 @@
      * considered at rest or false if it is still animating.
      */
     private boolean isAtEquilibrium() {
-        double displacement = mDistance * mHeight; // in pixels
-        return Math.abs(mVelocity) < VELOCITY_THRESHOLD
+        double displacement = mDistance * mHeight * LINEAR_STRETCH_INTENSITY; // in pixels
+        double velocity = mVelocity * LINEAR_STRETCH_INTENSITY;
+        return Math.abs(velocity) < VELOCITY_THRESHOLD
                 && Math.abs(displacement) < VALUE_THRESHOLD;
     }
 
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 319e788..cb2bba1 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -5476,14 +5476,14 @@
     /**
      * Object allowing the modification of a context to overload the system's dynamic colors.
      *
-     * Only colors from {@link android.R.color#system_primary_0} to
-     * {@link android.R.color#system_neutral_1000} can be overloaded.
+     * Only colors from {@link android.R.color#system_accent1_0} to
+     * {@link android.R.color#system_neutral2_1000} can be overloaded.
      * @hide
      */
     public static final class ColorResources {
         // Set of valid colors resources.
-        private static final int FIRST_RESOURCE_COLOR_ID = android.R.color.system_primary_0;
-        private static final int LAST_RESOURCE_COLOR_ID = android.R.color.system_neutral_1000;
+        private static final int FIRST_RESOURCE_COLOR_ID = android.R.color.system_neutral1_0;
+        private static final int LAST_RESOURCE_COLOR_ID = android.R.color.system_accent3_1000;
         // Size, in bytes, of an entry in the array of colors in an ARSC file.
         private static final int ARSC_ENTRY_SIZE = 16;
 
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index 3709aa1..9789d70 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -15,6 +15,7 @@
  */
 package android.window;
 
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 
 import android.annotation.ColorInt;
@@ -32,6 +33,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -183,6 +185,7 @@
          * Create SplashScreenWindowView object from materials.
          */
         public SplashScreenView build() {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "SplashScreenView#build");
             final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
             final SplashScreenView view = (SplashScreenView)
                     layoutInflater.inflate(R.layout.splash_screen_view, null, false);
@@ -208,14 +211,14 @@
                 view.mParceledIconBitmap = mParceledIconBitmap;
             }
             // branding image
-            if (mBrandingImageHeight > 0 && mBrandingImageWidth > 0) {
+            if (mBrandingImageHeight > 0 && mBrandingImageWidth > 0 && mBrandingDrawable != null) {
                 final ViewGroup.LayoutParams params = view.mBrandingImageView.getLayoutParams();
                 params.width = mBrandingImageWidth;
                 params.height = mBrandingImageHeight;
                 view.mBrandingImageView.setLayoutParams(params);
-            }
-            if (mBrandingDrawable != null) {
                 view.mBrandingImageView.setBackground(mBrandingDrawable);
+            } else {
+                view.mBrandingImageView.setVisibility(GONE);
             }
             if (mParceledBrandingBitmap != null) {
                 view.mParceledBrandingBitmap = mParceledBrandingBitmap;
@@ -226,6 +229,7 @@
                         + view.mBrandingImageView + " drawable: " + mBrandingDrawable
                         + " size w: " + mBrandingImageWidth + " h: " + mBrandingImageHeight);
             }
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
             return view;
         }
     }
diff --git a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
index 26af615..b76ef0f 100644
--- a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
+++ b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
@@ -28,11 +28,9 @@
 import android.content.pm.ResolveInfo;
 import android.os.Message;
 import android.os.UserHandle;
-import android.provider.DeviceConfig;
 import android.util.Log;
 
 import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -62,11 +60,6 @@
     // back to using the ResolverRankerService.
     private ResolverRankerServiceResolverComparator mResolverRankerService;
 
-    private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean(
-            DeviceConfig.NAMESPACE_SYSTEMUI,
-            SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
-            true);
-
     AppPredictionServiceResolverComparator(
                 Context context,
                 Intent intent,
@@ -183,9 +176,6 @@
         if (mResolverRankerService != null) {
             return mResolverRankerService.getScore(name);
         }
-        if (mAppendDirectShareEnabled && !mTargetScores.isEmpty()) {
-            return mTargetScores.get(name);
-        }
         Integer rank = mTargetRanks.get(name);
         if (rank == null) {
             Log.w(TAG, "Score requested for unknown component. Did you call compute yet?");
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index d4d8536..862c900 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -256,15 +256,6 @@
             SystemUiDeviceConfigFlags.HASH_SALT_MAX_DAYS,
             DEFAULT_SALT_EXPIRATION_DAYS);
 
-    private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean(
-            DeviceConfig.NAMESPACE_SYSTEMUI,
-            SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
-            true);
-    private boolean mChooserTargetRankingEnabled = DeviceConfig.getBoolean(
-            DeviceConfig.NAMESPACE_SYSTEMUI,
-            SystemUiDeviceConfigFlags.CHOOSER_TARGET_RANKING_ENABLED,
-            true);
-
     private Bundle mReplacementExtras;
     private IntentSender mChosenComponentSender;
     private IntentSender mRefinementIntentSender;
@@ -472,16 +463,10 @@
         private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 4;
         private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 5;
         private static final int LIST_VIEW_UPDATE_MESSAGE = 6;
-        private static final int CHOOSER_TARGET_RANKING_SCORE = 7;
 
         private static final int WATCHDOG_TIMEOUT_MAX_MILLIS = 10000;
         private static final int WATCHDOG_TIMEOUT_MIN_MILLIS = 3000;
 
-        private static final int DEFAULT_DIRECT_SHARE_TIMEOUT_MILLIS = 1500;
-        private int mDirectShareTimeout = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.SHARE_SHEET_DIRECT_SHARE_TIMEOUT,
-                DEFAULT_DIRECT_SHARE_TIMEOUT_MILLIS);
-
         private boolean mMinTimeoutPassed = false;
 
         private void removeAllMessages() {
@@ -491,7 +476,6 @@
             removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
             removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT);
             removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED);
-            removeMessages(CHOOSER_TARGET_RANKING_SCORE);
         }
 
         private void restartServiceRequestTimer() {
@@ -501,14 +485,13 @@
 
             if (DEBUG) {
                 Log.d(TAG, "queryTargets setting watchdog timer for "
-                        + mDirectShareTimeout + "-"
                         + WATCHDOG_TIMEOUT_MAX_MILLIS + "ms");
             }
 
             sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT,
                     WATCHDOG_TIMEOUT_MIN_MILLIS);
             sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT,
-                    mAppendDirectShareEnabled ? mDirectShareTimeout : WATCHDOG_TIMEOUT_MAX_MILLIS);
+                    WATCHDOG_TIMEOUT_MAX_MILLIS);
         }
 
         private void maybeStopServiceRequestTimer() {
@@ -608,17 +591,6 @@
                     getChooserActivityLogger().logSharesheetDirectLoadComplete();
                     break;
 
-                case CHOOSER_TARGET_RANKING_SCORE:
-                    if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_RANKING_SCORE");
-                    final ChooserTargetRankingInfo scoreInfo = (ChooserTargetRankingInfo) msg.obj;
-                    ChooserListAdapter adapterForUserHandle =
-                            mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(
-                                    scoreInfo.userHandle);
-                    if (adapterForUserHandle != null) {
-                        adapterForUserHandle.addChooserTargetRankingScore(scoreInfo.scores);
-                    }
-                    break;
-
                 default:
                     super.handleMessage(msg);
             }
@@ -878,24 +850,14 @@
             final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
                     new ArrayList<>();
 
-            // Separate ChooserTargets ranking scores and ranked Shortcuts.
             List<AppTarget> shortcutResults = new ArrayList<>();
-            List<AppTarget> chooserTargetScores = new ArrayList<>();
             for (AppTarget appTarget : resultList) {
                 if (appTarget.getShortcutInfo() == null) {
                     continue;
                 }
-                if (appTarget.getShortcutInfo().getId().equals(CHOOSER_TARGET)) {
-                    chooserTargetScores.add(appTarget);
-                } else {
-                    shortcutResults.add(appTarget);
-                }
+                shortcutResults.add(appTarget);
             }
             resultList = shortcutResults;
-            if (mChooserTargetRankingEnabled) {
-                sendChooserTargetRankingScore(chooserTargetScores,
-                        chooserListAdapter.getUserHandle());
-            }
             for (AppTarget appTarget : resultList) {
                 shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo(
                         appTarget.getShortcutInfo(),
@@ -2148,14 +2110,6 @@
         return true;
     }
 
-    private void sendChooserTargetRankingScore(List<AppTarget> chooserTargetScores,
-            UserHandle userHandle) {
-        final Message msg = Message.obtain();
-        msg.what = ChooserHandler.CHOOSER_TARGET_RANKING_SCORE;
-        msg.obj = new ChooserTargetRankingInfo(chooserTargetScores, userHandle);
-        mChooserHandler.sendMessage(msg);
-    }
-
     private void sendShareShortcutInfoList(
                 List<ShortcutManager.ShareShortcutInfo> resultList,
                 ChooserListAdapter chooserListAdapter,
@@ -2376,9 +2330,6 @@
     }
 
     private void sendImpressionToAppPredictor(TargetInfo targetInfo, ChooserListAdapter adapter) {
-        if (!mChooserTargetRankingEnabled) {
-            return;
-        }
         AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled(
                 mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
         if (directShareAppPredictor == null) {
@@ -2399,11 +2350,6 @@
                 targetIds.add(new AppTargetId(
                         String.format("%s/%s/%s", shortcutId, componentName.flattenToString(),
                                 SHORTCUT_TARGET)));
-            } else {
-                String titleHash = ChooserUtil.md5(chooserTarget.getTitle().toString());
-                targetIds.add(new AppTargetId(
-                        String.format("%s/%s/%s", titleHash, componentName.flattenToString(),
-                                CHOOSER_TARGET)));
             }
         }
         directShareAppPredictor.notifyLaunchLocationShown(LAUNCH_LOCATION_DIRECT_SHARE, targetIds);
@@ -2423,29 +2369,6 @@
         if (mDirectShareAppTargetCache != null) {
             appTarget = mDirectShareAppTargetCache.get(chooserTarget);
         }
-        if (mChooserTargetRankingEnabled && appTarget == null) {
-            // Send ChooserTarget sharing info to AppPredictor.
-            ComponentName componentName = mChooserTargetComponentNameCache.getOrDefault(
-                    chooserTarget.getComponentName(), chooserTarget.getComponentName());
-            try {
-                appTarget = new AppTarget.Builder(
-                        new AppTargetId(componentName.flattenToString()),
-                        new ShortcutInfo.Builder(
-                                createPackageContextAsUser(
-                                        componentName.getPackageName(),
-                                        0 /* flags */,
-                                        getUser()),
-                                CHOOSER_TARGET)
-                            .setActivity(componentName)
-                            .setShortLabel(ChooserUtil.md5(chooserTarget.getTitle().toString()))
-                            .build())
-                        .setClassName(componentName.getClassName())
-                        .build();
-            } catch (NameNotFoundException e) {
-                Log.e(TAG, "Could not look up service " + componentName
-                        + "; component name not found");
-            }
-        }
         // This is a direct share click that was provided by the APS
         if (appTarget != null) {
             directShareAppPredictor.notifyAppTargetEvent(
@@ -3971,9 +3894,7 @@
                 // until they start to scroll
                 ChooserListAdapter adapter =
                         mChooserMultiProfilePagerAdapter.getActiveListAdapter();
-                int validTargets =
-                        mAppendDirectShareEnabled ? adapter.getNumServiceTargetsForExpand()
-                                : adapter.getSelectableServiceTargetCount();
+                int validTargets = adapter.getSelectableServiceTargetCount();
                 if (validTargets <= maxTargetsPerRow) {
                     mHideDirectShareExpansion = true;
                     return;
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 5700668..cc2b12a 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -21,7 +21,6 @@
 
 import android.app.ActivityManager;
 import android.app.prediction.AppPredictor;
-import android.app.prediction.AppTarget;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -37,7 +36,6 @@
 import android.provider.DeviceConfig;
 import android.service.chooser.ChooserTarget;
 import android.util.Log;
-import android.util.Pair;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -53,21 +51,13 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
 
 public class ChooserListAdapter extends ResolverListAdapter {
     private static final String TAG = "ChooserListAdapter";
     private static final boolean DEBUG = false;
 
-    private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean(
-            DeviceConfig.NAMESPACE_SYSTEMUI,
-            SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
-            true);
-
     private boolean mEnableStackedApps = true;
 
     public static final int NO_POSITION = -1;
@@ -79,8 +69,6 @@
 
     private static final int MAX_SUGGESTED_APP_TARGETS = 4;
     private static final int MAX_CHOOSER_TARGETS_PER_APP = 2;
-    private static final int MAX_SERVICE_TARGET_APP = 8;
-    private static final int DEFAULT_DIRECT_SHARE_RANKING_SCORE = 1000;
 
     /** {@link #getBaseScore} */
     public static final float CALLER_TARGET_SCORE_BOOST = 900.f;
@@ -94,17 +82,11 @@
 
     private int mNumShortcutResults = 0;
     private Map<DisplayResolveInfo, LoadIconTask> mIconLoaders = new HashMap<>();
+    private boolean mApplySharingAppLimits;
 
     // Reserve spots for incoming direct share targets by adding placeholders
     private ChooserTargetInfo
             mPlaceHolderTargetInfo = new ChooserActivity.PlaceHolderTargetInfo();
-    private int mValidServiceTargetsNum = 0;
-    private int mAvailableServiceTargetsNum = 0;
-    private final Map<ComponentName, Pair<List<ChooserTargetInfo>, Integer>>
-            mParkingDirectShareTargets = new HashMap<>();
-    private final Map<ComponentName, Map<String, Integer>> mChooserTargetScores = new HashMap<>();
-    private Set<ComponentName> mPendingChooserTargetService = new HashSet<>();
-    private Set<ComponentName> mShortcutComponents = new HashSet<>();
     private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
     private final List<DisplayResolveInfo> mCallerTargets = new ArrayList<>();
 
@@ -183,6 +165,10 @@
                 if (mCallerTargets.size() == MAX_SUGGESTED_APP_TARGETS) break;
             }
         }
+        mApplySharingAppLimits = DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI,
+                true);
     }
 
     AppPredictor getAppPredictor() {
@@ -209,9 +195,6 @@
 
     void refreshListView() {
         if (mListViewDataChanged) {
-            if (mAppendDirectShareEnabled) {
-                appendServiceTargetsWithQuota();
-            }
             super.notifyDataSetChanged();
         }
         mListViewDataChanged = false;
@@ -221,10 +204,6 @@
     private void createPlaceHolders() {
         mNumShortcutResults = 0;
         mServiceTargets.clear();
-        mValidServiceTargetsNum = 0;
-        mParkingDirectShareTargets.clear();
-        mPendingChooserTargetService.clear();
-        mShortcutComponents.clear();
         for (int i = 0; i < mChooserListCommunicator.getMaxRankedTargets(); i++) {
             mServiceTargets.add(mPlaceHolderTargetInfo);
         }
@@ -517,33 +496,30 @@
                     + targets.size()
                     + " targets");
         }
-        if (mAppendDirectShareEnabled) {
-            parkTargetIntoMemory(origTarget, targets, targetType, directShareToShortcutInfos,
-                    pendingChooserTargetServiceConnections);
-            return;
-        }
         if (targets.size() == 0) {
             return;
         }
-
         final float baseScore = getBaseScore(origTarget, targetType);
         Collections.sort(targets, mBaseTargetComparator);
-
         final boolean isShortcutResult =
                 (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
                         || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
         final int maxTargets = isShortcutResult ? mMaxShortcutTargetsPerApp
                 : MAX_CHOOSER_TARGETS_PER_APP;
+        final int targetsLimit = mApplySharingAppLimits ? Math.min(targets.size(), maxTargets)
+                : targets.size();
         float lastScore = 0;
         boolean shouldNotify = false;
-        for (int i = 0, count = Math.min(targets.size(), maxTargets); i < count; i++) {
+        for (int i = 0, count = targetsLimit; i < count; i++) {
             final ChooserTarget target = targets.get(i);
             float targetScore = target.getScore();
-            targetScore *= baseScore;
-            if (i > 0 && targetScore >= lastScore) {
-                // Apply a decay so that the top app can't crowd out everything else.
-                // This incents ChooserTargetServices to define what's truly better.
-                targetScore = lastScore * 0.95f;
+            if (mApplySharingAppLimits) {
+                targetScore *= baseScore;
+                if (i > 0 && targetScore >= lastScore) {
+                    // Apply a decay so that the top app can't crowd out everything else.
+                    // This incents ChooserTargetServices to define what's truly better.
+                    targetScore = lastScore * 0.95f;
+                }
             }
             UserHandle userHandle = getUserHandle();
             Context contextAsUser = mContext.createContextAsUser(userHandle, 0 /* flags */);
@@ -561,7 +537,8 @@
                 Log.d(TAG, " => " + target.toString() + " score=" + targetScore
                         + " base=" + target.getScore()
                         + " lastScore=" + lastScore
-                        + " baseScore=" + baseScore);
+                        + " baseScore=" + baseScore
+                        + " applyAppLimit=" + mApplySharingAppLimits);
             }
 
             lastScore = targetScore;
@@ -573,217 +550,13 @@
     }
 
     /**
-     * Store ChooserTarget ranking scores info wrapped in {@code targets}.
-     */
-    public void addChooserTargetRankingScore(List<AppTarget> targets) {
-        Log.i(TAG, "addChooserTargetRankingScore " + targets.size() + " targets score.");
-        for (AppTarget target : targets) {
-            if (target.getShortcutInfo() == null) {
-                continue;
-            }
-            ShortcutInfo shortcutInfo = target.getShortcutInfo();
-            if (!shortcutInfo.getId().equals(ChooserActivity.CHOOSER_TARGET)
-                    || shortcutInfo.getActivity() == null) {
-                continue;
-            }
-            ComponentName componentName = shortcutInfo.getActivity();
-            if (!mChooserTargetScores.containsKey(componentName)) {
-                mChooserTargetScores.put(componentName, new HashMap<>());
-            }
-            mChooserTargetScores.get(componentName).put(shortcutInfo.getShortLabel().toString(),
-                    target.getRank());
-        }
-        mChooserTargetScores.keySet().forEach(key -> rankTargetsWithinComponent(key));
-    }
-
-    /**
-     * Rank chooserTargets of the given {@code componentName} in mParkingDirectShareTargets as per
-     * available scores stored in mChooserTargetScores.
-     */
-    private void rankTargetsWithinComponent(ComponentName componentName) {
-        if (!mParkingDirectShareTargets.containsKey(componentName)
-                || !mChooserTargetScores.containsKey(componentName)) {
-            return;
-        }
-        Map<String, Integer> scores = mChooserTargetScores.get(componentName);
-        Collections.sort(mParkingDirectShareTargets.get(componentName).first, (o1, o2) -> {
-            // The score has been normalized between 0 and 2000, the default is 1000.
-            int score1 = scores.getOrDefault(
-                    ChooserUtil.md5(o1.getChooserTarget().getTitle().toString()),
-                    DEFAULT_DIRECT_SHARE_RANKING_SCORE);
-            int score2 = scores.getOrDefault(
-                    ChooserUtil.md5(o2.getChooserTarget().getTitle().toString()),
-                    DEFAULT_DIRECT_SHARE_RANKING_SCORE);
-            return score2 - score1;
-        });
-    }
-
-    /**
-     * Park {@code targets} into memory for the moment to surface them later when view is refreshed.
-     * Components pending on ChooserTargetService query are also recorded.
-     */
-    private void parkTargetIntoMemory(DisplayResolveInfo origTarget, List<ChooserTarget> targets,
-            @ChooserActivity.ShareTargetType int targetType,
-            Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos,
-            List<ChooserActivity.ChooserTargetServiceConnection>
-                    pendingChooserTargetServiceConnections) {
-        ComponentName origComponentName = origTarget != null ? origTarget.getResolvedComponentName()
-                : !targets.isEmpty() ? targets.get(0).getComponentName() : null;
-        Log.i(TAG,
-                "parkTargetIntoMemory " + origComponentName + ", " + targets.size() + " targets");
-        mPendingChooserTargetService = pendingChooserTargetServiceConnections.stream()
-                .map(ChooserActivity.ChooserTargetServiceConnection::getComponentName)
-                .filter(componentName -> !componentName.equals(origComponentName))
-                .collect(Collectors.toSet());
-        // Park targets in memory
-        if (!targets.isEmpty()) {
-            final boolean isShortcutResult =
-                    (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
-                            || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
-            Context contextAsUser = mContext.createContextAsUser(getUserHandle(),
-                    0 /* flags */);
-            List<ChooserTargetInfo> parkingTargetInfos = targets.stream()
-                    .map(target ->
-                            new SelectableTargetInfo(
-                                    contextAsUser, origTarget, target, target.getScore(),
-                                    mSelectableTargetInfoCommunicator,
-                                    (isShortcutResult ? directShareToShortcutInfos.get(target)
-                                            : null))
-                    )
-                    .collect(Collectors.toList());
-            Pair<List<ChooserTargetInfo>, Integer> parkingTargetInfoPair =
-                    mParkingDirectShareTargets.getOrDefault(origComponentName,
-                            new Pair<>(new ArrayList<>(), 0));
-            for (ChooserTargetInfo target : parkingTargetInfos) {
-                if (!checkDuplicateTarget(target, parkingTargetInfoPair.first)
-                        && !checkDuplicateTarget(target, mServiceTargets)) {
-                    parkingTargetInfoPair.first.add(target);
-                    mAvailableServiceTargetsNum++;
-                }
-            }
-            mParkingDirectShareTargets.put(origComponentName, parkingTargetInfoPair);
-            rankTargetsWithinComponent(origComponentName);
-            if (isShortcutResult) {
-                mShortcutComponents.add(origComponentName);
-            }
-        }
-        notifyDataSetChanged();
-    }
-
-    /**
-     * Append targets of top ranked share app into direct share row with quota limit. Remove
-     * appended ones from memory.
-     */
-    private void appendServiceTargetsWithQuota() {
-        int maxRankedTargets = mChooserListCommunicator.getMaxRankedTargets();
-        List<ComponentName> topComponentNames = getTopComponentNames(maxRankedTargets);
-        float totalScore = 0f;
-        for (ComponentName component : topComponentNames) {
-            if (!mPendingChooserTargetService.contains(component)
-                    && !mParkingDirectShareTargets.containsKey(component)) {
-                continue;
-            }
-            totalScore += super.getScore(component);
-        }
-        boolean shouldWaitPendingService = false;
-        for (ComponentName component : topComponentNames) {
-            if (!mPendingChooserTargetService.contains(component)
-                    && !mParkingDirectShareTargets.containsKey(component)) {
-                continue;
-            }
-            float score = super.getScore(component);
-            int quota = Math.round(maxRankedTargets * score / totalScore);
-            if (mPendingChooserTargetService.contains(component) && quota >= 1) {
-                shouldWaitPendingService = true;
-            }
-            if (!mParkingDirectShareTargets.containsKey(component)) {
-                continue;
-            }
-            // Append targets into direct share row as per quota.
-            Pair<List<ChooserTargetInfo>, Integer> parkingTargetsItem =
-                    mParkingDirectShareTargets.get(component);
-            List<ChooserTargetInfo> parkingTargets = parkingTargetsItem.first;
-            int insertedNum = parkingTargetsItem.second;
-            while (insertedNum < quota && !parkingTargets.isEmpty()) {
-                if (!checkDuplicateTarget(parkingTargets.get(0), mServiceTargets)) {
-                    mServiceTargets.add(mValidServiceTargetsNum, parkingTargets.get(0));
-                    mValidServiceTargetsNum++;
-                    insertedNum++;
-                }
-                parkingTargets.remove(0);
-            }
-            Log.i(TAG, " appendServiceTargetsWithQuota component=" + component
-                    + " appendNum=" + (insertedNum - parkingTargetsItem.second));
-            if (DEBUG) {
-                Log.d(TAG, " appendServiceTargetsWithQuota component=" + component
-                        + " score=" + score
-                        + " totalScore=" + totalScore
-                        + " quota=" + quota);
-            }
-            mParkingDirectShareTargets.put(component, new Pair<>(parkingTargets, insertedNum));
-        }
-        if (!shouldWaitPendingService) {
-            fillAllServiceTargets();
-        }
-    }
-
-    /**
-     * Append all remaining targets (parking in memory) into direct share row as per their ranking.
-     */
-    private void fillAllServiceTargets() {
-        if (mParkingDirectShareTargets.isEmpty()) {
-            return;
-        }
-        Log.i(TAG, " fillAllServiceTargets");
-        List<ComponentName> topComponentNames = getTopComponentNames(MAX_SERVICE_TARGET_APP);
-        // Append all remaining targets of top recommended components into direct share row.
-        for (ComponentName component : topComponentNames) {
-            if (!mParkingDirectShareTargets.containsKey(component)) {
-                continue;
-            }
-            mParkingDirectShareTargets.get(component).first.stream()
-                    .filter(target -> !checkDuplicateTarget(target, mServiceTargets))
-                    .forEach(target -> {
-                        mServiceTargets.add(mValidServiceTargetsNum, target);
-                        mValidServiceTargetsNum++;
-                    });
-            mParkingDirectShareTargets.remove(component);
-        }
-        // Append all remaining shortcuts targets into direct share row.
-        mParkingDirectShareTargets.entrySet().stream()
-                .filter(entry -> mShortcutComponents.contains(entry.getKey()))
-                .map(entry -> entry.getValue())
-                .map(pair -> pair.first)
-                .forEach(targets -> {
-                    for (ChooserTargetInfo target : targets) {
-                        if (!checkDuplicateTarget(target, mServiceTargets)) {
-                            mServiceTargets.add(mValidServiceTargetsNum, target);
-                            mValidServiceTargetsNum++;
-                        }
-                    }
-                });
-        mParkingDirectShareTargets.clear();
-    }
-
-    private boolean checkDuplicateTarget(ChooserTargetInfo target,
-            List<ChooserTargetInfo> destination) {
-        // Check for duplicates and abort if found
-        for (ChooserTargetInfo otherTargetInfo : destination) {
-            if (target.isSimilar(otherTargetInfo)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
      * The return number have to exceed a minimum limit to make direct share area expandable. When
      * append direct share targets is enabled, return count of all available targets parking in the
      * memory; otherwise, it is shortcuts count which will help reduce the amount of visible
      * shuffling due to older-style direct share targets.
      */
     int getNumServiceTargetsForExpand() {
-        return mAppendDirectShareEnabled ? mAvailableServiceTargetsNum : mNumShortcutResults;
+        return mNumShortcutResults;
     }
 
     /**
@@ -801,16 +574,11 @@
         if (target == null) {
             return CALLER_TARGET_SCORE_BOOST;
         }
-
-        if (targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
-            return SHORTCUT_TARGET_SCORE_BOOST;
-        }
-
         float score = super.getScore(target);
-        if (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
+        if (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
+                || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
             return score * SHORTCUT_TARGET_SCORE_BOOST;
         }
-
         return score;
     }
 
@@ -820,9 +588,6 @@
      */
     public void completeServiceTargetLoading() {
         mServiceTargets.removeIf(o -> o instanceof ChooserActivity.PlaceHolderTargetInfo);
-        if (mAppendDirectShareEnabled) {
-            fillAllServiceTargets();
-        }
         if (mServiceTargets.isEmpty()) {
             mServiceTargets.add(new ChooserActivity.EmptyTargetInfo());
         }
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 52801fa..a5b894d 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -130,12 +130,6 @@
     // Flags related to media notifications
 
     /**
-     * (boolean) If {@code true}, enables the seekbar in compact media notifications.
-     */
-    public static final String COMPACT_MEDIA_SEEKBAR_ENABLED =
-            "compact_media_notification_seekbar_enabled";
-
-    /**
      * (int) Maximum number of days to retain the salt for hashing direct share targets in logging
      */
     public static final String HASH_SALT_MAX_DAYS = "hash_salt_max_days";
@@ -415,6 +409,12 @@
     public static final String CHOOSER_TARGET_RANKING_ENABLED = "chooser_target_ranking_enabled";
 
     /**
+     * (boolean) Whether dark launch of remote prediction service is enabled.
+     */
+    public static final String DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED =
+            "dark_launch_remote_prediction_service_enabled";
+
+    /**
      * (boolean) Whether to enable pinch resizing for PIP.
      */
     public static final String PIP_PINCH_RESIZE = "pip_pinch_resize";
@@ -473,6 +473,14 @@
      */
     public static final String SHARE_USE_SERVICE_TARGETS = "share_use_service_targets";
 
+    /**
+     * (boolean) If true, SysUI provides guardrails for app usage of Direct Share by enforcing
+     * limits on number of targets per app & adjusting scores for apps providing many targets. If
+     * false, this step is skipped. This should be true unless the ranking provider configured by
+     * [some other flag] is expected to manage these incentives.
+     */
+    public static final String APPLY_SHARING_APP_LIMITS_IN_SYSUI =
+            "apply_sharing_app_limits_in_sysui";
 
     /*
      * (long) The duration that the home button must be pressed before triggering Assist
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 7f87885..a043756 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -89,6 +89,7 @@
 import android.util.Printer;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseDoubleArray;
 import android.util.SparseIntArray;
 import android.util.SparseLongArray;
 import android.util.TimeUtils;
@@ -12548,81 +12549,6 @@
     }
 
     /**
-     * SparseDoubleArray map integers to doubles.
-     * Its implementation is the same as that of {@link SparseLongArray}; see there for details.
-     *
-     * @see SparseLongArray
-     */
-    private static class SparseDoubleArray {
-        /**
-         * The int->double map, but storing the doubles as longs using
-         * {@link Double.doubleToRawLongBits(double)}.
-         */
-        private final SparseLongArray mValues = new SparseLongArray();
-
-        /**
-         * Gets the double mapped from the specified key, or <code>0</code>
-         * if no such mapping has been made.
-         */
-        public double get(int key) {
-            if (mValues.indexOfKey(key) >= 0) {
-                return Double.longBitsToDouble(mValues.get(key));
-            }
-            return 0;
-        }
-
-        /**
-         * Adds a mapping from the specified key to the specified value,
-         * replacing the previous mapping from the specified key if there
-         * was one.
-         */
-        public void put(int key, double value) {
-            mValues.put(key, Double.doubleToRawLongBits(value));
-        }
-
-        /**
-         * Adds a mapping from the specified key to the specified value,
-         * <b>adding</b> to the previous mapping from the specified key if there
-         * was one.
-         */
-        public void add(int key, double summand) {
-            final double oldValue = get(key);
-            put(key, oldValue + summand);
-        }
-
-        /**
-         * Returns the number of key-value mappings that this SparseDoubleArray
-         * currently stores.
-         */
-        public int size() {
-            return mValues.size();
-        }
-
-        /**
-         * Given an index in the range <code>0...size()-1</code>, returns
-         * the key from the <code>index</code>th key-value mapping that this
-         * SparseDoubleArray stores.
-         *
-         * @see SparseLongArray#keyAt(int)
-         */
-        public int keyAt(int index) {
-            return mValues.keyAt(index);
-        }
-
-        /**
-         * Given an index in the range <code>0...size()-1</code>, returns
-         * the value from the <code>index</code>th key-value mapping that this
-         * SparseDoubleArray stores.
-         *
-         * @see SparseLongArray#valueAt(int)
-         */
-        public double valueAt(int index) {
-            return Double.longBitsToDouble(mValues.valueAt(index));
-        }
-
-    }
-
-    /**
      * Read and record Rail Energy data.
      */
     public void updateRailStatsLocked() {
diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl
index ec9a0a2..49dbbaa 100644
--- a/core/java/com/android/internal/view/IInputMethodClient.aidl
+++ b/core/java/com/android/internal/view/IInputMethodClient.aidl
@@ -28,7 +28,6 @@
     void setActive(boolean active, boolean fullscreen, boolean reportToImeController);
     void scheduleStartInputIfNecessary(boolean fullscreen);
     void reportFullscreenMode(boolean fullscreen);
-    void applyImeVisibility(boolean setVisible);
     void updateActivityViewToScreenMatrix(int bindSequence, in float[] matrixValues);
     void setImeTraceEnabled(boolean enabled);
 }
diff --git a/core/java/com/android/internal/widget/MediaNotificationView.java b/core/java/com/android/internal/widget/MediaNotificationView.java
index f42d5da..8ff3c10 100644
--- a/core/java/com/android/internal/widget/MediaNotificationView.java
+++ b/core/java/com/android/internal/widget/MediaNotificationView.java
@@ -19,31 +19,19 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.util.AttributeSet;
-import android.view.NotificationHeaderView;
-import android.view.View;
-import android.view.ViewGroup;
 import android.widget.FrameLayout;
-import android.widget.ImageView;
 import android.widget.RemoteViews;
 
 import java.util.ArrayList;
 
 /**
- * A TextView that can float around an image on the end.
+ * The Layout class which handles template details for the Notification.MediaStyle
  *
  * @hide
  */
 @RemoteViews.RemoteView
 public class MediaNotificationView extends FrameLayout {
 
-    private final int mNotificationContentMarginEnd;
-    private final int mNotificationContentImageMarginEnd;
-    private ImageView mRightIcon;
-    private View mActions;
-    private NotificationHeaderView mHeader;
-    private View mMainColumn;
-    private View mMediaContent;
-    private int mImagePushIn;
     private ArrayList<VisibilityChangeListener> mListeners;
 
     public MediaNotificationView(Context context) {
@@ -58,120 +46,14 @@
         this(context, attrs, defStyleAttr, 0);
     }
 
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        boolean hasIcon = mRightIcon.getVisibility() != GONE;
-        if (!hasIcon) {
-            resetHeaderIndention();
-        }
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        int mode = MeasureSpec.getMode(widthMeasureSpec);
-        boolean reMeasure = false;
-        mImagePushIn = 0;
-        if (hasIcon && mode != MeasureSpec.UNSPECIFIED) {
-            int size = MeasureSpec.getSize(widthMeasureSpec);
-            size = size - mActions.getMeasuredWidth();
-            ViewGroup.MarginLayoutParams layoutParams =
-                    (MarginLayoutParams) mRightIcon.getLayoutParams();
-            int imageEndMargin = layoutParams.getMarginEnd();
-            size -= imageEndMargin;
-            int fullHeight = mMediaContent.getMeasuredHeight();
-            if (size > fullHeight) {
-                size = fullHeight;
-            } else if (size < fullHeight) {
-                size = Math.max(0, size);
-                mImagePushIn = fullHeight - size;
-            }
-            if (layoutParams.width != fullHeight || layoutParams.height != fullHeight) {
-                layoutParams.width = fullHeight;
-                layoutParams.height = fullHeight;
-                mRightIcon.setLayoutParams(layoutParams);
-                reMeasure = true;
-            }
-
-            // lets ensure that the main column doesn't run into the image
-            ViewGroup.MarginLayoutParams params
-                    = (MarginLayoutParams) mMainColumn.getLayoutParams();
-            int marginEnd = size + imageEndMargin + mNotificationContentMarginEnd;
-            if (marginEnd != params.getMarginEnd()) {
-                params.setMarginEnd(marginEnd);
-                mMainColumn.setLayoutParams(params);
-                reMeasure = true;
-            }
-            // TODO(b/172652345): validate all this logic (especially positioning of expand button)
-            // margin for the entire header line
-            int headerMarginEnd = imageEndMargin;
-            // margin for the header text (not including the expand button and other icons)
-            int headerExtraMarginEnd = Math.max(0,
-                    size + imageEndMargin - mHeader.getTopLineBaseMarginEnd());
-            if (headerExtraMarginEnd != mHeader.getTopLineExtraMarginEnd()) {
-                mHeader.setTopLineExtraMarginEnd(headerExtraMarginEnd);
-                reMeasure = true;
-            }
-            params = (MarginLayoutParams) mHeader.getLayoutParams();
-            if (params.getMarginEnd() != headerMarginEnd) {
-                params.setMarginEnd(headerMarginEnd);
-                mHeader.setLayoutParams(params);
-                reMeasure = true;
-            }
-            if (mHeader.getPaddingEnd() != mNotificationContentImageMarginEnd) {
-                mHeader.setPaddingRelative(mHeader.getPaddingStart(),
-                        mHeader.getPaddingTop(),
-                        mNotificationContentImageMarginEnd,
-                        mHeader.getPaddingBottom());
-                reMeasure = true;
-            }
-        }
-        if (reMeasure) {
-            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        }
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        if (mImagePushIn > 0) {
-            if (this.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
-                mImagePushIn *= -1;
-            }
-            mRightIcon.layout(mRightIcon.getLeft() + mImagePushIn, mRightIcon.getTop(),
-                    mRightIcon.getRight()  + mImagePushIn, mRightIcon.getBottom());
-        }
-    }
-
-    private void resetHeaderIndention() {
-        if (mHeader.getPaddingEnd() != mNotificationContentMarginEnd) {
-            mHeader.setPaddingRelative(mHeader.getPaddingStart(),
-                    mHeader.getPaddingTop(),
-                    mNotificationContentMarginEnd,
-                    mHeader.getPaddingBottom());
-        }
-        ViewGroup.MarginLayoutParams headerParams =
-                (MarginLayoutParams) mHeader.getLayoutParams();
-        headerParams.setMarginEnd(0);
-        if (headerParams.getMarginEnd() != 0) {
-            headerParams.setMarginEnd(0);
-            mHeader.setLayoutParams(headerParams);
-        }
-    }
-
     public MediaNotificationView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        mNotificationContentMarginEnd = context.getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.notification_content_margin_end);
-        mNotificationContentImageMarginEnd = context.getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.notification_content_image_margin_end);
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mRightIcon = findViewById(com.android.internal.R.id.right_icon);
-        mActions = findViewById(com.android.internal.R.id.media_actions);
-        mHeader = findViewById(com.android.internal.R.id.notification_header);
-        mMainColumn = findViewById(com.android.internal.R.id.notification_main_column);
-        mMediaContent = findViewById(com.android.internal.R.id.notification_media_content);
     }
 
     @Override
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index f49a834..f1fa5db 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -238,6 +238,7 @@
                 "android.hardware.camera.device@3.2",
                 "media_permission-aidl-cpp",
                 "libandroidicu",
+                "libandroid_net",
                 "libbpf_android",
                 "libnetdbpf",
                 "libnetdutils",
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index b790056..b46b5a2 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -68,7 +68,7 @@
 };
 
 static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName, jlong surfaceControl,
-                          jlong width, jlong height, jint format, jboolean enableTripleBuffering) {
+                          jlong width, jlong height, jint format) {
     String8 str8;
     if (jName) {
         const jchar* str16 = env->GetStringCritical(jName, nullptr);
@@ -81,7 +81,7 @@
     std::string name = str8.string();
     sp<BLASTBufferQueue> queue =
             new BLASTBufferQueue(name, reinterpret_cast<SurfaceControl*>(surfaceControl), width,
-                                 height, format, enableTripleBuffering);
+                                 height, format);
     queue->incStrong((void*)nativeCreate);
     return reinterpret_cast<jlong>(queue.get());
 }
@@ -140,7 +140,7 @@
 static const JNINativeMethod gMethods[] = {
         /* name, signature, funcPtr */
         // clang-format off
-        {"nativeCreate", "(Ljava/lang/String;JJJIZ)J", (void*)nativeCreate},
+        {"nativeCreate", "(Ljava/lang/String;JJJI)J", (void*)nativeCreate},
         {"nativeGetSurface", "(JZ)Landroid/view/Surface;", (void*)nativeGetSurface},
         {"nativeDestroy", "(J)V", (void*)nativeDestroy},
         {"nativeSetNextTransaction", "(JJ)V", (void*)nativeSetNextTransaction},
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index d4b5c2b..dd62bb1 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -52,6 +52,7 @@
 #include <string.h>
 #include <sys/epoll.h>
 #include <sys/errno.h>
+#include <sys/pidfd.h>
 #include <sys/resource.h>
 #include <sys/stat.h>
 #include <sys/syscall.h>
@@ -1316,14 +1317,8 @@
     return removeAllProcessGroups();
 }
 
-// Wrapper function to the syscall pidfd_open, which creates a file
-// descriptor that refers to the process whose PID is specified in pid.
-static inline int sys_pidfd_open(pid_t pid, unsigned int flags) {
-    return syscall(__NR_pidfd_open, pid, flags);
-}
-
 static jint android_os_Process_nativePidFdOpen(JNIEnv* env, jobject, jint pid, jint flags) {
-    int fd = sys_pidfd_open(pid, flags);
+    int fd = pidfd_open(pid, flags);
     if (fd < 0) {
         jniThrowErrnoException(env, "nativePidFdOpen", errno);
         return -1;
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 52d21a8..10927b9 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -18,28 +18,28 @@
 
 //#define LOG_NDEBUG 0
 
-#include <nativehelper/JNIHelp.h>
-
 #include <android_runtime/AndroidRuntime.h>
-#include <log/log.h>
-#include <utils/Looper.h>
 #include <input/InputTransport.h>
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <utils/Looper.h>
 #include "android_os_MessageQueue.h"
 #include "android_view_InputChannel.h"
 #include "android_view_KeyEvent.h"
 #include "android_view_MotionEvent.h"
+#include "core_jni_helpers.h"
 
-#include <nativehelper/ScopedLocalRef.h>
+#include <inttypes.h>
 #include <unordered_map>
 
-#include "core_jni_helpers.h"
 
 using android::base::Result;
 
 namespace android {
 
 // Log debug messages about the dispatch cycle.
-static const bool kDebugDispatchCycle = false;
+static constexpr bool kDebugDispatchCycle = false;
 
 static struct {
     jclass clazz;
@@ -74,8 +74,10 @@
         return mInputPublisher.getChannel()->getName();
     }
 
-    virtual int handleEvent(int receiveFd, int events, void* data);
+    int handleEvent(int receiveFd, int events, void* data) override;
     status_t receiveFinishedSignals(JNIEnv* env);
+    bool notifyFinishedSignal(JNIEnv* env, jobject sender, const InputPublisher::Finished& finished,
+                              bool skipCallbacks);
 };
 
 NativeInputEventSender::NativeInputEventSender(JNIEnv* env, jobject senderWeak,
@@ -196,8 +198,13 @@
         ALOGD("channel '%s' ~ Receiving finished signals.", getInputChannelName().c_str());
     }
 
-    ScopedLocalRef<jobject> senderObj(env, NULL);
-    bool skipCallbacks = false;
+    ScopedLocalRef<jobject> senderObj(env, jniGetReferent(env, mSenderWeakGlobal));
+    if (!senderObj.get()) {
+        ALOGW("channel '%s' ~ Sender object was finalized without being disposed.",
+              getInputChannelName().c_str());
+        return DEAD_OBJECT;
+    }
+    bool skipCallbacks = false; // stop calling Java functions after an exception occurs
     for (;;) {
         Result<InputPublisher::Finished> result = mInputPublisher.receiveFinishedSignal();
         if (!result.ok()) {
@@ -206,45 +213,55 @@
                 return OK;
             }
             ALOGE("channel '%s' ~ Failed to consume finished signals.  status=%d",
-                    getInputChannelName().c_str(), status);
+                  getInputChannelName().c_str(), status);
             return status;
         }
 
-        auto it = mPublishedSeqMap.find(result->seq);
-        if (it == mPublishedSeqMap.end()) {
-            continue;
-        }
-
-        uint32_t seq = it->second;
-        mPublishedSeqMap.erase(it);
-
-        if (kDebugDispatchCycle) {
-            ALOGD("channel '%s' ~ Received finished signal, seq=%u, handled=%s, pendingEvents=%zu.",
-                  getInputChannelName().c_str(), seq, result->handled ? "true" : "false",
-                  mPublishedSeqMap.size());
-        }
-
-        if (!skipCallbacks) {
-            if (!senderObj.get()) {
-                senderObj.reset(jniGetReferent(env, mSenderWeakGlobal));
-                if (!senderObj.get()) {
-                    ALOGW("channel '%s' ~ Sender object was finalized without being disposed.",
-                          getInputChannelName().c_str());
-                    return DEAD_OBJECT;
-                }
-            }
-
-            env->CallVoidMethod(senderObj.get(),
-                                gInputEventSenderClassInfo.dispatchInputEventFinished,
-                                static_cast<jint>(seq), static_cast<jboolean>(result->handled));
-            if (env->ExceptionCheck()) {
-                ALOGE("Exception dispatching finished signal.");
-                skipCallbacks = true;
-            }
+        const bool notified = notifyFinishedSignal(env, senderObj.get(), *result, skipCallbacks);
+        if (!notified) {
+            skipCallbacks = true;
         }
     }
 }
 
+/**
+ * Invoke the Java function dispatchInputEventFinished for the received "Finished" signal.
+ * Set the variable 'skipCallbacks' to 'true' if a Java exception occurred.
+ * Java function will only be called if 'skipCallbacks' is originally 'false'.
+ *
+ * Return "false" if an exception occurred while calling the Java function
+ *        "true" otherwise
+ */
+bool NativeInputEventSender::notifyFinishedSignal(JNIEnv* env, jobject sender,
+                                                  const InputPublisher::Finished& finished,
+                                                  bool skipCallbacks) {
+    auto it = mPublishedSeqMap.find(finished.seq);
+    if (it == mPublishedSeqMap.end()) {
+        ALOGW("Received 'finished' signal for unknown seq number = %" PRIu32, finished.seq);
+        // Since this is coming from the receiver (typically app), it's possible that an app
+        // does something wrong and sends bad data. Just ignore and process other events.
+        return true;
+    }
+    const uint32_t seq = it->second;
+    mPublishedSeqMap.erase(it);
+
+    if (kDebugDispatchCycle) {
+        ALOGD("channel '%s' ~ Received finished signal, seq=%u, handled=%s, pendingEvents=%zu.",
+              getInputChannelName().c_str(), seq, finished.handled ? "true" : "false",
+              mPublishedSeqMap.size());
+    }
+    if (skipCallbacks) {
+        return true;
+    }
+
+    env->CallVoidMethod(sender, gInputEventSenderClassInfo.dispatchInputEventFinished,
+                        static_cast<jint>(seq), static_cast<jboolean>(finished.handled));
+    if (env->ExceptionCheck()) {
+        ALOGE("Exception dispatching finished signal for seq=%" PRIu32, seq);
+        return false;
+    }
+    return true;
+}
 
 static jlong nativeInit(JNIEnv* env, jclass clazz, jobject senderWeak,
         jobject inputChannelObj, jobject messageQueueObj) {
diff --git a/core/proto/android/server/biometrics.proto b/core/proto/android/server/biometrics.proto
index bbb0edd..4f3ae28 100644
--- a/core/proto/android/server/biometrics.proto
+++ b/core/proto/android/server/biometrics.proto
@@ -178,4 +178,6 @@
     CM_DETECT_INTERACTION = 13;
     CM_INVALIDATION_REQUESTER = 14;
     CM_INVALIDATE = 15;
+    CM_STOP_USER = 16;
+    CM_START_USER = 17;
 }
\ No newline at end of file
diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto
index aab054f..16c3ab0 100644
--- a/core/proto/android/server/vibrator/vibratormanagerservice.proto
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -21,40 +21,38 @@
 
 import "frameworks/base/core/proto/android/privacy.proto";
 
-message OneShotProto {
-    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-    repeated int32 duration = 1;
-    repeated int32 amplitude = 2;
-}
-
-message WaveformProto {
+message StepSegmentProto {
    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-   repeated int32 timings = 1;
-   repeated int32 amplitudes = 2;
-   required bool repeat = 3;
+   optional int32 duration = 1;
+   optional float amplitude = 2;
 }
 
-message PrebakedProto {
+message PrebakedSegmentProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
     optional int32 effect_id = 1;
     optional int32 effect_strength = 2;
     optional int32 fallback = 3;
 }
 
-message ComposedProto {
+message PrimitiveSegmentProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-    repeated int32 effect_ids = 1;
-    repeated float effect_scales = 2;
-    repeated int32 delays = 3;
+    optional int32 primitive_id = 1;
+    optional float scale = 2;
+    optional int32 delay = 3;
+}
+
+message SegmentProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    optional PrebakedSegmentProto prebaked = 1;
+    optional PrimitiveSegmentProto primitive = 2;
+    optional StepSegmentProto step = 3;
 }
 
 // A com.android.os.VibrationEffect object.
 message VibrationEffectProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-    optional OneShotProto oneshot = 1;
-    optional WaveformProto waveform = 2;
-    optional PrebakedProto prebaked = 3;
-    optional ComposedProto composed = 4;
+    optional SegmentProto segments = 1;
+    required int32 repeat = 2;
 }
 
 message SyncVibrationEffectProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 45fd96a..8d7f542 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2659,10 +2659,6 @@
     <permission android:name="android.permission.CREATE_USERS"
         android:protectionLevel="signature" />
 
-    <!-- @TestApi @hide Allows an application to query user info for all users on the device. -->
-    <permission android:name="android.permission.QUERY_USERS"
-                android:protectionLevel="signature" />
-
     <!-- @hide Allows an application to set the profile owners and the device owner.
          This permission is not available to third party applications.-->
     <permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"
@@ -3447,6 +3443,14 @@
     <permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"
         android:protectionLevel="signature" />
 
+    <!-- Allows an application to avoid all toast rate limiting restrictions.
+         <p>Not for use by third-party applications.
+         @hide
+    -->
+    <permission android:name="android.permission.UNLIMITED_TOASTS"
+                android:protectionLevel="signature" />
+    <uses-permission android:name="android.permission.UNLIMITED_TOASTS" />
+
     <!-- @SystemApi Allows an application to use
          {@link android.view.WindowManager.LayoutsParams#SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS}
          to hide non-system-overlay windows.
@@ -3550,7 +3554,7 @@
          @hide
     -->
     <permission android:name="android.permission.GET_TOP_ACTIVITY_INFO"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|recents" />
 
     <!-- Allows an application to retrieve the current state of keys and
          switches.
@@ -4484,7 +4488,7 @@
     <permission android:name="android.permission.FACTORY_TEST"
         android:protectionLevel="signature" />
 
-    <!-- @hide @TestApi Allows an application to broadcast the intent {@link
+    <!-- @hide @TestApi @SystemApi Allows an application to broadcast the intent {@link
          android.content.Intent#ACTION_CLOSE_SYSTEM_DIALOGS}.
          <p>Not for use by third-party applications.
     -->
diff --git a/core/res/remote_color_resources_res/values/colors.xml b/core/res/remote_color_resources_res/values/colors.xml
index 295f16e..e4bcae43 100644
--- a/core/res/remote_color_resources_res/values/colors.xml
+++ b/core/res/remote_color_resources_res/values/colors.xml
@@ -1,40 +1,64 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
   <!-- Note: the values of the colors doesn't really matter (they will always be overwritten before used), but they help a lot debugging, to find out which color is where in the ARSC file. -->
-  <color name="system_primary_0">#01010101</color>
-  <color name="system_primary_50">#02020202</color>
-  <color name="system_primary_100">#03030303</color>
-  <color name="system_primary_200">#04040404</color>
-  <color name="system_primary_300">#05050505</color>
-  <color name="system_primary_400">#06060606</color>
-  <color name="system_primary_500">#07070707</color>
-  <color name="system_primary_600">#08080808</color>
-  <color name="system_primary_700">#09090909</color>
-  <color name="system_primary_800">#0a0a0a0a</color>
-  <color name="system_primary_900">#0b0b0b0b</color>
-  <color name="system_primary_1000">#0c0c0c0c</color>
-  <color name="system_secondary_0">#10101010</color>
-  <color name="system_secondary_50">#20202020</color>
-  <color name="system_secondary_100">#30303030</color>
-  <color name="system_secondary_200">#40404040</color>
-  <color name="system_secondary_300">#50505050</color>
-  <color name="system_secondary_400">#60606060</color>
-  <color name="system_secondary_500">#70707070</color>
-  <color name="system_secondary_600">#80808080</color>
-  <color name="system_secondary_700">#90909090</color>
-  <color name="system_secondary_800">#a0a0a0a0</color>
-  <color name="system_secondary_900">#b0b0b0b0</color>
-  <color name="system_secondary_1000">#c0c0c0c0</color>
-  <color name="system_neutral_0">#1f1f1f1f</color>
-  <color name="system_neutral_50">#2f2f2f2f</color>
-  <color name="system_neutral_100">#3f3f3f3f</color>
-  <color name="system_neutral_200">#4f4f4f4f</color>
-  <color name="system_neutral_300">#5f5f5f5f</color>
-  <color name="system_neutral_400">#6f6f6f6f</color>
-  <color name="system_neutral_500">#7f7f7f7f</color>
-  <color name="system_neutral_600">#8f8f8f8f</color>
-  <color name="system_neutral_700">#9f9f9f9f</color>
-  <color name="system_neutral_800">#afafafaf</color>
-  <color name="system_neutral_900">#bfbfbfbf</color>
-  <color name="system_neutral_1000">#cfcfcfcf</color>
+  <color name="system_accent1_0">#ffffff</color>
+  <color name="system_accent1_50">#91fff4</color>
+  <color name="system_accent1_100">#83f6e5</color>
+  <color name="system_accent1_200">#65d9c9</color>
+  <color name="system_accent1_300">#45bdae</color>
+  <color name="system_accent1_400">#1fa293</color>
+  <color name="system_accent1_500">#008377</color>
+  <color name="system_accent1_600">#006d61</color>
+  <color name="system_accent1_700">#005449</color>
+  <color name="system_accent1_800">#003c33</color>
+  <color name="system_accent1_900">#00271e</color>
+  <color name="system_accent1_1000">#000000</color>
+  <color name="system_accent2_0">#ffffff</color>
+  <color name="system_accent2_50">#91fff4</color>
+  <color name="system_accent2_100">#83f6e5</color>
+  <color name="system_accent2_200">#65d9c9</color>
+  <color name="system_accent2_300">#45bdae</color>
+  <color name="system_accent2_400">#1fa293</color>
+  <color name="system_accent2_500">#008377</color>
+  <color name="system_accent2_600">#006d61</color>
+  <color name="system_accent2_700">#005449</color>
+  <color name="system_accent2_800">#003c33</color>
+  <color name="system_accent2_900">#00271e</color>
+  <color name="system_accent2_1000">#000000</color>
+  <color name="system_accent3_0">#ffffff</color>
+  <color name="system_accent3_50">#91fff4</color>
+  <color name="system_accent3_100">#83f6e5</color>
+  <color name="system_accent3_200">#65d9c9</color>
+  <color name="system_accent3_300">#45bdae</color>
+  <color name="system_accent3_400">#1fa293</color>
+  <color name="system_accent3_500">#008377</color>
+  <color name="system_accent3_600">#006d61</color>
+  <color name="system_accent3_700">#005449</color>
+  <color name="system_accent3_800">#003c33</color>
+  <color name="system_accent3_900">#00271e</color>
+  <color name="system_accent3_1000">#000000</color>
+  <color name="system_neutral1_0">#ffffff</color>
+  <color name="system_neutral1_50">#f0f0f0</color>
+  <color name="system_neutral1_100">#e2e2e2</color>
+  <color name="system_neutral1_200">#c6c6c6</color>
+  <color name="system_neutral1_300">#ababab</color>
+  <color name="system_neutral1_400">#909090</color>
+  <color name="system_neutral1_500">#757575</color>
+  <color name="system_neutral1_600">#5e5e5e</color>
+  <color name="system_neutral1_700">#464646</color>
+  <color name="system_neutral1_800">#303030</color>
+  <color name="system_neutral1_900">#1b1b1b</color>
+  <color name="system_neutral1_1000">#000000</color>
+  <color name="system_neutral2_0">#ffffff</color>
+  <color name="system_neutral2_50">#f0f0f0</color>
+  <color name="system_neutral2_100">#e2e2e2</color>
+  <color name="system_neutral2_200">#c6c6c6</color>
+  <color name="system_neutral2_300">#ababab</color>
+  <color name="system_neutral2_400">#909090</color>
+  <color name="system_neutral2_500">#757575</color>
+  <color name="system_neutral2_600">#5e5e5e</color>
+  <color name="system_neutral2_700">#464646</color>
+  <color name="system_neutral2_800">#303030</color>
+  <color name="system_neutral2_900">#1b1b1b</color>
+  <color name="system_neutral2_1000">#000000</color>
 </resources>
diff --git a/core/res/remote_color_resources_res/values/public.xml b/core/res/remote_color_resources_res/values/public.xml
index e628f09..9616628 100644
--- a/core/res/remote_color_resources_res/values/public.xml
+++ b/core/res/remote_color_resources_res/values/public.xml
@@ -1,41 +1,65 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
   <public-group type="color" first-id="0x0106001d">
-    <public name="system_primary_0" />
-    <public name="system_primary_50" />
-    <public name="system_primary_100" />
-    <public name="system_primary_200" />
-    <public name="system_primary_300" />
-    <public name="system_primary_400" />
-    <public name="system_primary_500" />
-    <public name="system_primary_600" />
-    <public name="system_primary_700" />
-    <public name="system_primary_800" />
-    <public name="system_primary_900" />
-    <public name="system_primary_1000" />
-    <public name="system_secondary_0" />
-    <public name="system_secondary_50" />
-    <public name="system_secondary_100" />
-    <public name="system_secondary_200" />
-    <public name="system_secondary_300" />
-    <public name="system_secondary_400" />
-    <public name="system_secondary_500" />
-    <public name="system_secondary_600" />
-    <public name="system_secondary_700" />
-    <public name="system_secondary_800" />
-    <public name="system_secondary_900" />
-    <public name="system_secondary_1000" />
-    <public name="system_neutral_0" />
-    <public name="system_neutral_50" />
-    <public name="system_neutral_100" />
-    <public name="system_neutral_200" />
-    <public name="system_neutral_300" />
-    <public name="system_neutral_400" />
-    <public name="system_neutral_500" />
-    <public name="system_neutral_600" />
-    <public name="system_neutral_700" />
-    <public name="system_neutral_800" />
-    <public name="system_neutral_900" />
-    <public name="system_neutral_1000" />
+    <public name="system_accent1_0" />
+    <public name="system_accent1_50" />
+    <public name="system_accent1_100" />
+    <public name="system_accent1_200" />
+    <public name="system_accent1_300" />
+    <public name="system_accent1_400" />
+    <public name="system_accent1_500" />
+    <public name="system_accent1_600" />
+    <public name="system_accent1_700" />
+    <public name="system_accent1_800" />
+    <public name="system_accent1_900" />
+    <public name="system_accent1_1000" />
+    <public name="system_accent2_0" />
+    <public name="system_accent2_50" />
+    <public name="system_accent2_100" />
+    <public name="system_accent2_200" />
+    <public name="system_accent2_300" />
+    <public name="system_accent2_400" />
+    <public name="system_accent2_500" />
+    <public name="system_accent2_600" />
+    <public name="system_accent2_700" />
+    <public name="system_accent2_800" />
+    <public name="system_accent2_900" />
+    <public name="system_accent2_1000" />
+    <public name="system_accent3_0" />
+    <public name="system_accent3_50" />
+    <public name="system_accent3_100" />
+    <public name="system_accent3_200" />
+    <public name="system_accent3_300" />
+    <public name="system_accent3_400" />
+    <public name="system_accent3_500" />
+    <public name="system_accent3_600" />
+    <public name="system_accent3_700" />
+    <public name="system_accent3_800" />
+    <public name="system_accent3_900" />
+    <public name="system_accent3_1000" />
+    <public name="system_neutral1_0" />
+    <public name="system_neutral1_50" />
+    <public name="system_neutral1_100" />
+    <public name="system_neutral1_200" />
+    <public name="system_neutral1_300" />
+    <public name="system_neutral1_400" />
+    <public name="system_neutral1_500" />
+    <public name="system_neutral1_600" />
+    <public name="system_neutral1_700" />
+    <public name="system_neutral1_800" />
+    <public name="system_neutral1_900" />
+    <public name="system_neutral1_1000" />
+    <public name="system_neutral2_0" />
+    <public name="system_neutral2_50" />
+    <public name="system_neutral2_100" />
+    <public name="system_neutral2_200" />
+    <public name="system_neutral2_300" />
+    <public name="system_neutral2_400" />
+    <public name="system_neutral2_500" />
+    <public name="system_neutral2_600" />
+    <public name="system_neutral2_700" />
+    <public name="system_neutral2_800" />
+    <public name="system_neutral2_900" />
+    <public name="system_neutral2_1000" />
   </public-group>
 </resources>
diff --git a/core/res/res/color/text_color_primary_device_default_dark.xml b/core/res/res/color/text_color_primary_device_default_dark.xml
index 90d6b07..5926fde 100644
--- a/core/res/res/color/text_color_primary_device_default_dark.xml
+++ b/core/res/res/color/text_color_primary_device_default_dark.xml
@@ -17,7 +17,6 @@
 <!-- Please see primary_text_material_dark.xml -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_enabled="false"
-          android:alpha="?attr/disabledAlpha"
-          android:color="@color/system_primary_50"/>
-    <item android:color="@color/system_primary_50"/>
+          android:color="@color/system_neutral1_500"/>
+    <item android:color="@color/system_neutral1_50"/>
 </selector>
diff --git a/core/res/res/color/text_color_primary_device_default_light.xml b/core/res/res/color/text_color_primary_device_default_light.xml
index bdc4fa9..1379523 100644
--- a/core/res/res/color/text_color_primary_device_default_light.xml
+++ b/core/res/res/color/text_color_primary_device_default_light.xml
@@ -17,7 +17,6 @@
 <!-- Please see primary_text_material_light.xml -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_enabled="false"
-          android:alpha="?attr/disabledAlpha"
-          android:color="@color/system_primary_900"/>
-    <item android:color="@color/system_primary_900"/>
+          android:color="@color/system_neutral1_400"/>
+    <item android:color="@color/system_neutral1_900"/>
 </selector>
diff --git a/core/res/res/color/text_color_secondary_device_default_dark.xml b/core/res/res/color/text_color_secondary_device_default_dark.xml
index 799636ad..f79fd62 100644
--- a/core/res/res/color/text_color_secondary_device_default_dark.xml
+++ b/core/res/res/color/text_color_secondary_device_default_dark.xml
@@ -18,6 +18,6 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_enabled="false"
           android:alpha="?attr/disabledAlpha"
-          android:color="@color/system_primary_200"/>
-    <item android:color="@color/system_primary_200"/>
+          android:color="@color/system_neutral2_200"/>
+    <item android:color="@color/system_neutral2_200"/>
 </selector>
diff --git a/core/res/res/color/text_color_secondary_device_default_light.xml b/core/res/res/color/text_color_secondary_device_default_light.xml
index 4793bb8..c58f565 100644
--- a/core/res/res/color/text_color_secondary_device_default_light.xml
+++ b/core/res/res/color/text_color_secondary_device_default_light.xml
@@ -18,6 +18,6 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_enabled="false"
           android:alpha="?attr/disabledAlpha"
-          android:color="@color/system_primary_700"/>
-    <item android:color="@color/system_primary_700"/>
+          android:color="@color/system_neutral2_700"/>
+    <item android:color="@color/system_neutral2_700"/>
 </selector>
diff --git a/core/res/res/color/text_color_tertiary_device_default_dark.xml b/core/res/res/color/text_color_tertiary_device_default_dark.xml
index c828631..63fdc81 100644
--- a/core/res/res/color/text_color_tertiary_device_default_dark.xml
+++ b/core/res/res/color/text_color_tertiary_device_default_dark.xml
@@ -18,6 +18,6 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_enabled="false"
           android:alpha="?attr/disabledAlpha"
-          android:color="@color/system_primary_400"/>
-    <item android:color="@color/system_primary_400"/>
+          android:color="@color/system_neutral2_400"/>
+    <item android:color="@color/system_neutral2_400"/>
 </selector>
diff --git a/core/res/res/color/text_color_tertiary_device_default_light.xml b/core/res/res/color/text_color_tertiary_device_default_light.xml
index 82c420a..1ad6f6a 100644
--- a/core/res/res/color/text_color_tertiary_device_default_light.xml
+++ b/core/res/res/color/text_color_tertiary_device_default_light.xml
@@ -18,6 +18,6 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_enabled="false"
           android:alpha="?attr/disabledAlpha"
-          android:color="@color/system_primary_500"/>
-    <item android:color="@color/system_primary_500"/>
+          android:color="@color/system_neutral2_500"/>
+    <item android:color="@color/system_neutral2_500"/>
 </selector>
diff --git a/core/res/res/layout/notification_material_media_action.xml b/core/res/res/layout/notification_material_media_action.xml
index dd79a0b..5f1b60e 100644
--- a/core/res/res/layout/notification_material_media_action.xml
+++ b/core/res/res/layout/notification_material_media_action.xml
@@ -24,7 +24,6 @@
     android:paddingTop="8dp"
     android:paddingStart="8dp"
     android:paddingEnd="8dp"
-    android:layout_marginEnd="2dp"
     android:gravity="center"
     android:background="@drawable/notification_material_media_action_background"
     android:visibility="gone"
diff --git a/core/res/res/layout/notification_material_media_seekbar.xml b/core/res/res/layout/notification_material_media_seekbar.xml
deleted file mode 100644
index 4aa8acc..0000000
--- a/core/res/res/layout/notification_material_media_seekbar.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2019 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/notification_media_progress"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="vertical"
-    android:layout_alignParentBottom="true"
-    >
-    <SeekBar android:id="@+id/notification_media_progress_bar"
-        style="@style/Widget.ProgressBar.Horizontal"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:maxHeight="3dp"
-        android:paddingTop="24dp"
-        android:paddingBottom="24dp"
-        android:clickable="true"
-        android:layout_marginBottom="-24dp"
-        android:layout_marginTop="-12dp"
-        android:splitTrack="false"
-    />
-    <FrameLayout
-        android:id="@+id/notification_media_progress_time"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:layout_marginBottom="11dp"
-        >
-
-        <!-- width is set to "match_parent" to avoid extra layout calls -->
-        <TextView android:id="@+id/notification_media_elapsed_time"
-            style="@style/Widget.DeviceDefault.Notification.Text"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentLeft="true"
-            android:layout_marginStart="@dimen/notification_content_margin_start"
-            android:gravity="start"
-        />
-
-        <TextView android:id="@+id/notification_media_total_time"
-            style="@style/Widget.DeviceDefault.Notification.Text"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentRight="true"
-            android:layout_marginEnd="@dimen/notification_content_margin_end"
-            android:gravity="end"
-        />
-    </FrameLayout>
-</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index bad9a6b..e644cd5 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -46,20 +46,6 @@
         android:padding="@dimen/notification_icon_circle_padding"
         />
 
-    <ImageView
-        android:id="@+id/right_icon"
-        android:layout_width="@dimen/notification_right_icon_size"
-        android:layout_height="@dimen/notification_right_icon_size"
-        android:layout_gravity="center_vertical|end"
-        android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
-        android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
-        android:layout_marginEnd="@dimen/notification_header_expand_icon_size"
-        android:background="@drawable/notification_large_icon_outline"
-        android:clipToOutline="true"
-        android:importantForAccessibility="no"
-        android:scaleType="centerCrop"
-        />
-
     <FrameLayout
         android:id="@+id/alternate_expand_target"
         android:layout_width="@dimen/notification_content_margin_start"
@@ -68,95 +54,116 @@
         android:importantForAccessibility="no"
         />
 
-    <FrameLayout
-        android:id="@+id/expand_button_touch_container"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout_gravity="end">
-
-        <include layout="@layout/notification_expand_button"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical|end"
-            />
-
-    </FrameLayout>
-
     <LinearLayout
-        android:id="@+id/notification_headerless_view_column"
+        android:id="@+id/notification_headerless_view_row"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:layout_marginBottom="@dimen/notification_headerless_margin_twoline"
-        android:layout_marginTop="@dimen/notification_headerless_margin_twoline"
-        android:orientation="vertical"
+        android:layout_height="match_parent"
+        android:layout_marginStart="@dimen/notification_content_margin_start"
+        android:orientation="horizontal"
         >
 
-        <!-- extends ViewGroup -->
-        <NotificationTopLineView
-            android:id="@+id/notification_top_line"
-            android:layout_width="wrap_content"
-            android:layout_height="@dimen/notification_headerless_line_height"
-            android:layout_marginEnd="@dimen/notification_heading_margin_end"
-            android:layout_marginStart="@dimen/notification_content_margin_start"
-            android:clipChildren="false"
-            android:theme="@style/Theme.DeviceDefault.Notification"
-            >
-
-            <!--
-            NOTE: The notification_top_line_views layout contains the app_name_text.
-            In order to include the title view at the beginning, the Notification.Builder
-            has logic to hide that view whenever this title view is to be visible.
-            -->
-
-            <TextView
-                android:id="@+id/title"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginEnd="@dimen/notification_header_separating_margin"
-                android:ellipsize="marquee"
-                android:fadingEdge="horizontal"
-                android:singleLine="true"
-                android:textAlignment="viewStart"
-                android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
-                />
-
-            <include layout="@layout/notification_top_line_views" />
-
-        </NotificationTopLineView>
-
         <LinearLayout
-            android:id="@+id/notification_main_column"
-            android:layout_width="match_parent"
+            android:id="@+id/notification_headerless_view_column"
+            android:layout_width="0px"
             android:layout_height="wrap_content"
-            android:layout_marginEnd="@dimen/notification_heading_margin_end"
-            android:layout_marginStart="@dimen/notification_content_margin_start"
+            android:layout_gravity="center_vertical"
+            android:layout_weight="1"
+            android:layout_marginBottom="@dimen/notification_headerless_margin_twoline"
+            android:layout_marginTop="@dimen/notification_headerless_margin_twoline"
             android:orientation="vertical"
             >
 
-            <com.android.internal.widget.NotificationVanishingFrameLayout
-                android:layout_width="match_parent"
+            <NotificationTopLineView
+                android:id="@+id/notification_top_line"
+                android:layout_width="wrap_content"
                 android:layout_height="@dimen/notification_headerless_line_height"
+                android:clipChildren="false"
+                android:theme="@style/Theme.DeviceDefault.Notification"
                 >
-                <!-- This is the simplest way to keep this text vertically centered without using
-                 gravity="center_vertical" which causes jumpiness in expansion animations. -->
-                <include
-                    layout="@layout/notification_template_text"
-                    android:layout_width="match_parent"
-                    android:layout_height="@dimen/notification_text_height"
-                    android:layout_gravity="center_vertical"
-                    android:layout_marginTop="0dp"
-                    />
-            </com.android.internal.widget.NotificationVanishingFrameLayout>
 
-            <include
-                layout="@layout/notification_template_progress"
+                <!--
+                NOTE: The notification_top_line_views layout contains the app_name_text.
+                In order to include the title view at the beginning, the Notification.Builder
+                has logic to hide that view whenever this title view is to be visible.
+                -->
+
+                <TextView
+                    android:id="@+id/title"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginEnd="@dimen/notification_header_separating_margin"
+                    android:ellipsize="marquee"
+                    android:fadingEdge="horizontal"
+                    android:singleLine="true"
+                    android:textAlignment="viewStart"
+                    android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+                    />
+
+                <include layout="@layout/notification_top_line_views" />
+
+            </NotificationTopLineView>
+
+            <LinearLayout
+                android:id="@+id/notification_main_column"
                 android:layout_width="match_parent"
-                android:layout_height="@dimen/notification_headerless_line_height"
-                />
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                >
+
+                <com.android.internal.widget.NotificationVanishingFrameLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/notification_headerless_line_height"
+                    >
+                    <!-- This is the simplest way to keep this text vertically centered without
+                     gravity="center_vertical" which causes jumpiness in expansion animations. -->
+                    <include
+                        layout="@layout/notification_template_text"
+                        android:layout_width="match_parent"
+                        android:layout_height="@dimen/notification_text_height"
+                        android:layout_gravity="center_vertical"
+                        android:layout_marginTop="0dp"
+                        />
+                </com.android.internal.widget.NotificationVanishingFrameLayout>
+
+                <include
+                    layout="@layout/notification_template_progress"
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/notification_headerless_line_height"
+                    />
+
+            </LinearLayout>
 
         </LinearLayout>
 
+        <ImageView
+            android:id="@+id/right_icon"
+            android:layout_width="@dimen/notification_right_icon_size"
+            android:layout_height="@dimen/notification_right_icon_size"
+            android:layout_gravity="center_vertical|end"
+            android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+            android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+            android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+            android:background="@drawable/notification_large_icon_outline"
+            android:clipToOutline="true"
+            android:importantForAccessibility="no"
+            android:scaleType="centerCrop"
+            />
+
+        <FrameLayout
+            android:id="@+id/expand_button_touch_container"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:minWidth="@dimen/notification_content_margin_end"
+            >
+
+            <include layout="@layout/notification_expand_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical|end"
+                />
+
+        </FrameLayout>
+
     </LinearLayout>
 
 </com.android.internal.widget.NotificationMaxHeightFrameLayout>
diff --git a/core/res/res/layout/notification_template_material_big_media.xml b/core/res/res/layout/notification_template_material_big_media.xml
index aa20ad3..ff64315 100644
--- a/core/res/res/layout/notification_template_material_big_media.xml
+++ b/core/res/res/layout/notification_template_material_big_media.xml
@@ -20,17 +20,8 @@
     android:id="@+id/status_bar_latest_event_content"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:background="#00000000"
     android:tag="bigMediaNarrow"
     >
-    <!-- The size will actually be determined at runtime -->
-    <ImageView
-        android:id="@+id/right_icon"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:layout_gravity="top|end"
-        android:scaleType="centerCrop"
-        />
 
     <include
         layout="@layout/notification_template_header"
@@ -49,46 +40,27 @@
             android:id="@+id/notification_main_column"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_marginTop="46dp"
+            android:layout_marginTop="@dimen/notification_content_margin_top"
             android:layout_marginStart="@dimen/notification_content_margin_start"
             android:layout_marginBottom="@dimen/notification_content_margin"
             android:layout_marginEnd="@dimen/notification_content_margin_end"
             android:orientation="vertical"
             >
-            <!-- TODO(b/172652345): fix the media style -->
-            <!--<include layout="@layout/notification_template_part_line1"/>-->
-            <!--<include layout="@layout/notification_template_text"/>-->
-
-            <TextView android:id="@+id/title"
-                android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:singleLine="true"
-                android:ellipsize="marquee"
-                android:fadingEdge="horizontal"
-                android:textAlignment="viewStart"
-                />
-
-            <com.android.internal.widget.ImageFloatingTextView
-                style="@style/Widget.DeviceDefault.Notification.Text"
-                android:id="@+id/text"
-                android:layout_width="match_parent"
-                android:layout_height="@dimen/notification_text_height"
-                android:layout_gravity="top"
-                android:layout_marginTop="0.5dp"
-                android:ellipsize="marquee"
-                android:fadingEdge="horizontal"
-                android:gravity="top"
-                android:singleLine="true"
-                android:textAlignment="viewStart"
-                />
+            <include layout="@layout/notification_template_part_line1"/>
+            <include layout="@layout/notification_template_text"/>
         </LinearLayout>
 
         <LinearLayout
             android:id="@+id/media_actions"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="-21dp"
+            android:paddingStart="44dp"
+            android:paddingEnd="44dp"
+            android:paddingBottom="@dimen/media_notification_actions_padding_bottom"
+            android:gravity="top"
             android:orientation="horizontal"
             android:layoutDirection="ltr"
-            style="@style/NotificationMediaActionContainer"
             >
 
             <include
@@ -117,10 +89,8 @@
                 />
         </LinearLayout>
 
-        <ViewStub
-            android:id="@+id/notification_media_seekbar_container"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            />
     </LinearLayout>
+
+    <include layout="@layout/notification_template_right_icon" />
+
 </com.android.internal.widget.MediaNotificationView>
diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml
index 542e59d..2991b17 100644
--- a/core/res/res/layout/notification_template_material_media.xml
+++ b/core/res/res/layout/notification_template_material_media.xml
@@ -19,101 +19,173 @@
     android:id="@+id/status_bar_latest_event_content"
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:background="#00000000"
+    android:layout_height="@dimen/notification_min_height"
     android:tag="media"
     >
-    <ImageView android:id="@+id/right_icon"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:adjustViewBounds="true"
-        android:layout_gravity="top|end"
+
+
+    <ImageView
+        android:id="@+id/left_icon"
+        android:layout_width="@dimen/notification_left_icon_size"
+        android:layout_height="@dimen/notification_left_icon_size"
+        android:layout_gravity="center_vertical|start"
+        android:layout_marginStart="@dimen/notification_left_icon_start"
+        android:background="@drawable/notification_large_icon_outline"
+        android:clipToOutline="true"
+        android:importantForAccessibility="no"
         android:scaleType="centerCrop"
-    />
-    <include layout="@layout/notification_template_header"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/media_notification_header_height"
+        android:visibility="gone"
         />
+
+    <com.android.internal.widget.CachingIconView
+        android:id="@+id/icon"
+        android:layout_width="@dimen/notification_icon_circle_size"
+        android:layout_height="@dimen/notification_icon_circle_size"
+        android:layout_gravity="center_vertical|start"
+        android:layout_marginStart="@dimen/notification_icon_circle_start"
+        android:background="@drawable/notification_icon_circle"
+        android:padding="@dimen/notification_icon_circle_padding"
+        />
+
+    <FrameLayout
+        android:id="@+id/alternate_expand_target"
+        android:layout_width="@dimen/notification_content_margin_start"
+        android:layout_height="match_parent"
+        android:layout_gravity="start"
+        android:importantForAccessibility="no"
+        />
+
     <LinearLayout
+        android:id="@+id/notification_headerless_view_row"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        android:id="@+id/notification_media_content"
+        android:layout_height="match_parent"
+        android:layout_marginStart="@dimen/notification_content_margin_start"
+        android:orientation="horizontal"
         >
+
         <LinearLayout
-            android:id="@+id/notification_main_column"
-            android:layout_width="match_parent"
+            android:id="@+id/notification_headerless_view_column"
+            android:layout_width="0px"
             android:layout_height="wrap_content"
-            android:orientation="horizontal"
-            android:layout_marginStart="@dimen/notification_content_margin_start"
-            android:layout_marginTop="46dp"
-            android:layout_alignParentTop="true"
-            android:tag="media"
+            android:layout_gravity="center_vertical"
+            android:layout_weight="1"
+            android:layout_marginBottom="@dimen/notification_headerless_margin_twoline"
+            android:layout_marginTop="@dimen/notification_headerless_margin_twoline"
+            android:orientation="vertical"
             >
+
+            <NotificationTopLineView
+                android:id="@+id/notification_top_line"
+                android:layout_width="wrap_content"
+                android:layout_height="@dimen/notification_headerless_line_height"
+                android:clipChildren="false"
+                android:theme="@style/Theme.DeviceDefault.Notification"
+                >
+
+                <!--
+                NOTE: The notification_top_line_views layout contains the app_name_text.
+                In order to include the title view at the beginning, the Notification.Builder
+                has logic to hide that view whenever this title view is to be visible.
+                -->
+
+                <TextView
+                    android:id="@+id/title"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginEnd="@dimen/notification_header_separating_margin"
+                    android:ellipsize="marquee"
+                    android:fadingEdge="horizontal"
+                    android:singleLine="true"
+                    android:textAlignment="viewStart"
+                    android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+                    />
+
+                <include layout="@layout/notification_top_line_views" />
+
+            </NotificationTopLineView>
+
             <LinearLayout
-                android:id="@+id/notification_content_container"
+                android:id="@+id/notification_main_column"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:layout_gravity="fill_vertical"
-                android:layout_weight="1"
-                android:paddingBottom="@dimen/notification_content_margin"
                 android:orientation="vertical"
                 >
-                <!-- TODO(b/172652345): fix the media style -->
-                <!--<include layout="@layout/notification_template_part_line1"/>-->
-                <!--<include layout="@layout/notification_template_text"/>-->
 
-                <TextView android:id="@+id/title"
-                    android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+                <com.android.internal.widget.NotificationVanishingFrameLayout
                     android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:singleLine="true"
-                    android:ellipsize="marquee"
-                    android:fadingEdge="horizontal"
-                    android:textAlignment="viewStart"
+                    android:layout_height="@dimen/notification_headerless_line_height"
+                    >
+                    <!-- This is the simplest way to keep this text vertically centered without
+                     gravity="center_vertical" which causes jumpiness in expansion animations. -->
+                    <include
+                        layout="@layout/notification_template_text"
+                        android:layout_width="match_parent"
+                        android:layout_height="@dimen/notification_text_height"
+                        android:layout_gravity="center_vertical"
+                        android:layout_marginTop="0dp"
+                        />
+                </com.android.internal.widget.NotificationVanishingFrameLayout>
+
+                <include
+                    layout="@layout/notification_template_progress"
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/notification_headerless_line_height"
                     />
 
-                <com.android.internal.widget.ImageFloatingTextView
-                    style="@style/Widget.DeviceDefault.Notification.Text"
-                    android:id="@+id/text"
-                    android:layout_width="match_parent"
-                    android:layout_height="@dimen/notification_text_height"
-                    android:layout_gravity="top"
-                    android:layout_marginTop="0.5dp"
-                    android:ellipsize="marquee"
-                    android:fadingEdge="horizontal"
-                    android:gravity="top"
-                    android:singleLine="true"
-                    android:textAlignment="viewStart"
-                    />
             </LinearLayout>
-            <LinearLayout
-                android:id="@+id/media_actions"
+
+        </LinearLayout>
+
+        <ImageView
+            android:id="@+id/right_icon"
+            android:layout_width="@dimen/notification_right_icon_size"
+            android:layout_height="@dimen/notification_right_icon_size"
+            android:layout_gravity="center_vertical|end"
+            android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+            android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+            android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+            android:background="@drawable/notification_large_icon_outline"
+            android:clipToOutline="true"
+            android:importantForAccessibility="no"
+            android:scaleType="centerCrop"
+            />
+
+        <LinearLayout
+            android:id="@+id/media_actions"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layoutDirection="ltr"
+            android:orientation="horizontal"
+            >
+            <include
+                layout="@layout/notification_material_media_action"
+                android:id="@+id/action0"
+                />
+            <include
+                layout="@layout/notification_material_media_action"
+                android:id="@+id/action1"
+                />
+            <include
+                layout="@layout/notification_material_media_action"
+                android:id="@+id/action2"
+                />
+        </LinearLayout>
+
+        <FrameLayout
+            android:id="@+id/expand_button_touch_container"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:minWidth="@dimen/notification_content_margin_end"
+            >
+
+            <include layout="@layout/notification_expand_button"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_gravity="top|end"
-                android:layout_marginStart="10dp"
-                android:layoutDirection="ltr"
-                android:orientation="horizontal"
-                >
-                <include
-                    layout="@layout/notification_material_media_action"
-                    android:id="@+id/action0"
+                android:layout_gravity="center_vertical|end"
                 />
-                <include
-                    layout="@layout/notification_material_media_action"
-                    android:id="@+id/action1"
-                />
-                <include
-                    layout="@layout/notification_material_media_action"
-                    android:id="@+id/action2"
-                />
-            </LinearLayout>
-        </LinearLayout>
-        <ViewStub android:id="@+id/notification_media_seekbar_container"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentBottom="true"
-        />
+
+        </FrameLayout>
+
     </LinearLayout>
 </com.android.internal.widget.MediaNotificationView>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 22467e4..748e3b1 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -244,114 +244,188 @@
 
     <color name="conversation_important_highlight">#F9AB00</color>
 
-    <!-- Lightest shade of the primary color used by the system. White.
+    <!-- Lightest shade of the accent color used by the system. White.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_primary_0">#ffffff</color>
-    <!-- Shade of the primary system color at 95% lightness.
+    <color name="system_accent1_0">#ffffff</color>
+    <!-- Shade of the accent system color at 95% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_primary_50">#f2f2f2</color>
-    <!-- Shade of the primary system color at 90% lightness.
+    <color name="system_accent1_50">#9CFFF2</color>
+    <!-- Shade of the accent system color at 90% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_primary_100">#e3e3e3</color>
-    <!-- Shade of the primary system color at 80% lightness.
+    <color name="system_accent1_100">#8DF5E3</color>
+    <!-- Shade of the accent system color at 80% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_primary_200">#c7c7c7</color>
-    <!-- Shade of the primary system color at 70% lightness.
+    <color name="system_accent1_200">#71D8C7</color>
+    <!-- Shade of the accent system color at 70% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_primary_300">#ababab</color>
-    <!-- Shade of the primary system color at 60% lightness.
+    <color name="system_accent1_300">#53BCAC</color>
+    <!-- Shade of the accent system color at 60% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_primary_400">#8f8f8f</color>
-    <!-- Shade of the primary system color at 50% lightness.
+    <color name="system_accent1_400">#34A192</color>
+    <!-- Shade of the accent system color at 49% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_primary_500">#757575</color>
-    <!-- Shade of the primary system color at 40% lightness.
+    <color name="system_accent1_500">#008375</color>
+    <!-- Shade of the accent system color at 40% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_primary_600">#5e5e5e</color>
-    <!-- Shade of the primary system color at 30% lightness.
+    <color name="system_accent1_600">#006C5F</color>
+    <!-- Shade of the accent system color at 30% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_primary_700">#474747</color>
-    <!-- Shade of the primary system color at 20% lightness.
+    <color name="system_accent1_700">#005747</color>
+    <!-- Shade of the accent system color at 20% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_primary_800">#303030</color>
-    <!-- Shade of the primary system color at 10% lightness.
+    <color name="system_accent1_800">#003E31</color>
+    <!-- Shade of the accent system color at 10% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_primary_900">#1f1f1f</color>
-    <!-- Darkest shade of the primary color used by the system. Black.
+    <color name="system_accent1_900">#002214</color>
+    <!-- Darkest shade of the accent color used by the system. Black.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_primary_1000">#000000</color>
+    <color name="system_accent1_1000">#000000</color>
 
-    <!-- Lightest shade of the secondary color used by the system. White.
+    <!-- Lightest shade of the secondary accent color used by the system. White.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_secondary_0">#ffffff</color>
-    <!-- Shade of the secondary system color at 95% lightness.
+    <color name="system_accent2_0">#ffffff</color>
+    <!-- Shade of the secondary accent system color at 95% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_secondary_50">#91fff4</color>
-    <!-- Shade of the secondary system color at 90% lightness.
+    <color name="system_accent2_50">#CDFAF1</color>
+    <!-- Shade of the secondary accent system color at 90% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_secondary_100">#83f6e5</color>
-    <!-- Shade of the secondary system color at 80% lightness.
+    <color name="system_accent2_100">#BFEBE3</color>
+    <!-- Shade of the secondary accent system color at 80% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_secondary_200">#65d9c9</color>
-    <!-- Shade of the secondary system color at 70% lightness.
+    <color name="system_accent2_200">#A4CFC7</color>
+    <!-- Shade of the secondary accent system color at 70% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_secondary_300">#45bdae</color>
-    <!-- Shade of the secondary system color at 60% lightness.
+    <color name="system_accent2_300">#89B4AC</color>
+    <!-- Shade of the secondary accent system color at 60% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_secondary_400">#1fa293</color>
-    <!-- Shade of the secondary system color at 50% lightness.
+    <color name="system_accent2_400">#6F9991</color>
+    <!-- Shade of the secondary accent system color at 49% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_secondary_500">#008377</color>
-    <!-- Shade of the secondary system color at 40% lightness.
+    <color name="system_accent2_500">#537C75</color>
+    <!-- Shade of the secondary accent system color at 40% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_secondary_600">#006d61</color>
-    <!-- Shade of the secondary system color at 30% lightness.
+    <color name="system_accent2_600">#3D665F</color>
+    <!-- Shade of the secondary accent system color at 30% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_secondary_700">#005449</color>
-    <!-- Shade of the secondary system color at 20% lightness.
+    <color name="system_accent2_700">#254E47</color>
+    <!-- Shade of the secondary accent system color at 20% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_secondary_800">#003c33</color>
-    <!-- Shade of the secondary system color at 10% lightness.
+    <color name="system_accent2_800">#0C3731</color>
+    <!-- Shade of the secondary accent system color at 10% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_secondary_900">#00271e</color>
-    <!-- Darkest shade of the secondary color used by the system. Black.
+    <color name="system_accent2_900">#00211C</color>
+    <!-- Darkest shade of the secondary accent color used by the system. Black.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_secondary_1000">#000000</color>
+    <color name="system_accent2_1000">#000000</color>
+
+    <!-- Lightest shade of the tertiary accent color used by the system. White.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_accent3_0">#ffffff</color>
+    <!-- Shade of the tertiary accent system color at 95% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_accent3_50">#F9EAFF</color>
+    <!-- Shade of the tertiary accent system color at 90% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_accent3_100">#ECDBFF</color>
+    <!-- Shade of the tertiary accent system color at 80% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_accent3_200">#CFBFEB</color>
+    <!-- Shade of the tertiary accent system color at 70% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_accent3_300">#B3A4CF</color>
+    <!-- Shade of the tertiary accent system color at 60% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_accent3_400">#988AB3</color>
+    <!-- Shade of the tertiary accent system color at 49% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_accent3_500">#7B6E96</color>
+    <!-- Shade of the tertiary accent system color at 40% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_accent3_600">#64587F</color>
+    <!-- Shade of the tertiary accent system color at 30% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_accent3_700">#4C4165</color>
+    <!-- Shade of the tertiary accent system color at 20% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_accent3_800">#352B4D</color>
+    <!-- Shade of the tertiary accent system color at 10% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_accent3_900">#1E1636</color>
+    <!-- Darkest shade of the tertiary accent color used by the system. Black.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_accent3_1000">#000000</color>
 
     <!-- Lightest shade of the neutral color used by the system. White.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral_0">#ffffff</color>
+    <color name="system_neutral1_0">#ffffff</color>
     <!-- Shade of the neutral system color at 95% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral_50">#f0f0f0</color>
+    <color name="system_neutral1_50">#f0f0f0</color>
     <!-- Shade of the neutral system color at 90% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral_100">#e2e2e2</color>
+    <color name="system_neutral1_100">#e2e2e2</color>
     <!-- Shade of the neutral system color at 80% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral_200">#c6c6c6</color>
+    <color name="system_neutral1_200">#c6c6c6</color>
     <!-- Shade of the neutral system color at 70% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral_300">#ababab</color>
+    <color name="system_neutral1_300">#ababab</color>
     <!-- Shade of the neutral system color at 60% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral_400">#909090</color>
-    <!-- Shade of the neutral system color at 50% lightness.
+    <color name="system_neutral1_400">#909090</color>
+    <!-- Shade of the neutral system color at 49% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral_500">#757575</color>
+    <color name="system_neutral1_500">#757575</color>
     <!-- Shade of the neutral system color at 40% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral_600">#5e5e5e</color>
+    <color name="system_neutral1_600">#5e5e5e</color>
     <!-- Shade of the neutral system color at 30% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral_700">#464646</color>
+    <color name="system_neutral1_700">#464646</color>
     <!-- Shade of the neutral system color at 20% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral_800">#303030</color>
+    <color name="system_neutral1_800">#303030</color>
     <!-- Shade of the neutral system color at 10% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral_900">#1b1b1b</color>
+    <color name="system_neutral1_900">#1b1b1b</color>
     <!-- Darkest shade of the neutral color used by the system. Black.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_neutral_1000">#000000</color>
+    <color name="system_neutral1_1000">#000000</color>
+
+    <!-- Lightest shade of the secondary neutral color used by the system. White.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral2_0">#ffffff</color>
+    <!-- Shade of the secondary neutral system color at 95% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral2_50">#f0f0f0</color>
+    <!-- Shade of the secondary neutral system color at 90% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral2_100">#e2e2e2</color>
+    <!-- Shade of the secondary neutral system color at 80% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral2_200">#c6c6c6</color>
+    <!-- Shade of the secondary neutral system color at 70% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral2_300">#ababab</color>
+    <!-- Shade of the secondary neutral system color at 60% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral2_400">#909090</color>
+    <!-- Shade of the secondary neutral system color at 49% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral2_500">#757575</color>
+    <!-- Shade of the secondary neutral system color at 40% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral2_600">#5e5e5e</color>
+    <!-- Shade of the secondary neutral system color at 30% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral2_700">#464646</color>
+    <!-- Shade of the secondary neutral system color at 20% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral2_800">#303030</color>
+    <!-- Shade of the secondary neutral system color at 10% lightness.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral2_900">#1b1b1b</color>
+    <!-- Darkest shade of the secondary neutral color used by the system. Black.
+     This value can be overlaid at runtime by OverlayManager RROs. -->
+    <color name="system_neutral2_1000">#000000</color>
 </resources>
diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml
index 9b56321..3fbd7ca 100644
--- a/core/res/res/values/colors_device_defaults.xml
+++ b/core/res/res/values/colors_device_defaults.xml
@@ -17,9 +17,9 @@
 <!-- Colors specific to DeviceDefault themes. These are mostly pass-throughs to enable
      overlaying new theme colors. -->
 <resources>
-    <color name="primary_device_default_dark">@color/system_primary_800</color>
-    <color name="primary_device_default_light">@color/system_primary_50</color>
-    <color name="primary_device_default_settings">@color/system_primary_800</color>
+    <color name="primary_device_default_dark">@color/system_neutral1_800</color>
+    <color name="primary_device_default_light">@color/system_neutral1_50</color>
+    <color name="primary_device_default_settings">@color/system_neutral1_800</color>
     <color name="primary_device_default_settings_light">@color/primary_device_default_light</color>
     <color name="primary_dark_device_default_dark">@color/primary_device_default_dark</color>
     <color name="primary_dark_device_default_light">@color/primary_device_default_light</color>
@@ -33,14 +33,14 @@
     <color name="tertiary_device_default_settings">@color/tertiary_material_settings</color>
     <color name="quaternary_device_default_settings">@color/quaternary_material_settings</color>
 
-    <color name="accent_device_default_light">@color/system_secondary_600</color>
-    <color name="accent_device_default_dark">@color/system_secondary_200</color>
+    <color name="accent_device_default_light">@color/system_accent1_600</color>
+    <color name="accent_device_default_dark">@color/system_accent1_200</color>
     <color name="accent_device_default">@color/accent_device_default_light</color>
 
-    <color name="background_device_default_dark">@color/system_primary_800</color>
-    <color name="background_device_default_light">@color/system_primary_50</color>
-    <color name="background_floating_device_default_dark">@color/system_primary_900</color>
-    <color name="background_floating_device_default_light">@color/system_primary_100</color>
+    <color name="background_device_default_dark">@color/system_neutral1_800</color>
+    <color name="background_device_default_light">@color/system_neutral1_50</color>
+    <color name="background_floating_device_default_dark">@color/system_neutral1_900</color>
+    <color name="background_floating_device_default_light">@color/system_neutral1_100</color>
 
     <!-- Please refer to text_color_[primary]_device_default_[light].xml for text colors-->
     <color name="foreground_device_default_light">@color/text_color_primary_device_default_light</color>
@@ -50,8 +50,8 @@
     <color name="error_color_device_default_dark">@color/error_color_material_dark</color>
     <color name="error_color_device_default_light">@color/error_color_material_light</color>
 
-    <color name="list_divider_color_light">@color/system_primary_500</color>
-    <color name="list_divider_color_dark">@color/system_primary_400</color>
+    <color name="list_divider_color_light">@color/system_neutral1_200</color>
+    <color name="list_divider_color_dark">@color/system_neutral1_700</color>
     <color name="list_divider_opacity_device_default_light">@android:color/white</color>
     <color name="list_divider_opacity_device_default_dark">@android:color/white</color>
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b4ef63f..a3b3bb5 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -951,6 +951,11 @@
     -->
     <integer name="config_longPressOnPowerBehavior">1</integer>
 
+    <!-- Whether the setting to change long press on power behaviour from default to assistant (5)
+         is available in Settings.
+     -->
+    <bool name="config_longPressOnPowerForAssistantSettingAvailable">true</bool>
+
     <!-- Control the behavior when the user long presses the power button for a long time.
             0 - Nothing
             1 - Global actions menu
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 43dbd38..afbbe46 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -226,9 +226,6 @@
     <!-- The margin on the end of the top-line content views (accommodates the expander) -->
     <dimen name="notification_heading_margin_end">56dp</dimen>
 
-    <!-- The margin for text at the end of the image view for media notifications -->
-    <dimen name="notification_media_image_margin_end">72dp</dimen>
-
     <!-- The height of the notification action list -->
     <dimen name="notification_action_list_height">60dp</dimen>
 
@@ -345,6 +342,9 @@
     <!-- The minimum width of the app name in the header if it shrinks -->
     <dimen name="notification_header_shrink_min_width">72dp</dimen>
 
+    <!-- The minimum width of optional header fields below which the view is simply hidden -->
+    <dimen name="notification_header_shrink_hide_width">24sp</dimen>
+
     <!-- The size of the media actions in the media notification. -->
     <dimen name="media_notification_action_button_size">48dp</dimen>
 
@@ -360,9 +360,6 @@
     <!-- The absolute height for the header in a media notification. -->
     <dimen name="media_notification_header_height">@dimen/notification_header_height</dimen>
 
-    <!-- The margin of the content to an image-->
-    <dimen name="notification_content_image_margin_end">8dp</dimen>
-
     <!-- The padding at the end of actions when the snooze and bubble buttons are gone-->
     <dimen name="snooze_and_bubble_gone_padding_end">12dp</dimen>
 
@@ -483,9 +480,6 @@
     <!-- Top padding for notification when text is large and narrow (i.e. it has 3 lines -->
     <dimen name="notification_top_pad_large_text_narrow">-4dp</dimen>
 
-    <!-- Padding for notification icon when drawn with circle around it -->
-    <dimen name="notification_large_icon_circle_padding">11dp</dimen>
-
     <!-- The margin on top of the text of the notification -->
     <dimen name="notification_text_margin_top">6dp</dimen>
 
@@ -736,12 +730,10 @@
     <dimen name="notification_big_picture_max_height">284dp</dimen>
     <!-- The maximum width of a big picture in a notification. The images will be reduced to that width in case they are bigger. This value is determined by the standard panel size -->
     <dimen name="notification_big_picture_max_width">416dp</dimen>
-    <!-- The maximum height of a image in a media notification. The images will be reduced to that height in case they are bigger. This value is determined by the expanded media template-->
-    <dimen name="notification_media_image_max_height">140dp</dimen>
-    <!-- The maximum width of a image in a media notification. The images will be reduced to that width in case they are bigger.-->
-    <dimen name="notification_media_image_max_width">280dp</dimen>
     <!-- The size of the right icon -->
     <dimen name="notification_right_icon_size">48dp</dimen>
+    <!-- The margin between the right icon and the content. -->
+    <dimen name="notification_right_icon_content_margin">12dp</dimen>
     <!-- The top and bottom margin of the right icon in the normal notification states -->
     <dimen name="notification_right_icon_headerless_margin">20dp</dimen>
     <!-- The top margin of the right icon in the "big" notification states -->
@@ -762,10 +754,6 @@
     <dimen name="notification_big_picture_max_height_low_ram">208dp</dimen>
     <!-- The maximum width of a big picture in a notification. The images will be reduced to that width in case they are bigger. -->
     <dimen name="notification_big_picture_max_width_low_ram">294dp</dimen>
-    <!-- The maximum height of a image in a media notification. The images will be reduced to that height in case they are bigger. -->
-    <dimen name="notification_media_image_max_height_low_ram">100dp</dimen>
-    <!-- The maximum width of a image in a media notification. The images will be reduced to that width in case they are bigger.-->
-    <dimen name="notification_media_image_max_width_low_ram">100dp</dimen>
     <!-- The size of the right icon image when on low ram -->
     <dimen name="notification_right_icon_size_low_ram">@dimen/notification_right_icon_size</dimen>
     <!-- The maximum size of the grayscale icon -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 3e5fad8..7694faf 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3102,45 +3102,68 @@
     <!-- color definitions go here -->
 
     <!-- Material design dynamic system palette:-->
-    <!-- Primary color -->
-    <public name="system_primary_0" />
-    <public name="system_primary_50" />
-    <public name="system_primary_100" />
-    <public name="system_primary_200" />
-    <public name="system_primary_300" />
-    <public name="system_primary_400" />
-    <public name="system_primary_500" />
-    <public name="system_primary_600" />
-    <public name="system_primary_700" />
-    <public name="system_primary_800" />
-    <public name="system_primary_900" />
-    <public name="system_primary_1000" />
-    <!-- Secondary color -->
-    <public name="system_secondary_0" />
-    <public name="system_secondary_50" />
-    <public name="system_secondary_100" />
-    <public name="system_secondary_200" />
-    <public name="system_secondary_300" />
-    <public name="system_secondary_400" />
-    <public name="system_secondary_500" />
-    <public name="system_secondary_600" />
-    <public name="system_secondary_700" />
-    <public name="system_secondary_800" />
-    <public name="system_secondary_900" />
-    <public name="system_secondary_1000" />
-    <!-- Neutral color -->
-    <public name="system_neutral_0" />
-    <public name="system_neutral_50" />
-    <public name="system_neutral_100" />
-    <public name="system_neutral_200" />
-    <public name="system_neutral_300" />
-    <public name="system_neutral_400" />
-    <public name="system_neutral_500" />
-    <public name="system_neutral_600" />
-    <public name="system_neutral_700" />
-    <public name="system_neutral_800" />
-    <public name="system_neutral_900" />
-    <public name="system_neutral_1000" />
+    <!-- Neutral colors for background and text -->
+    <public name="system_neutral1_0" />
+    <public name="system_neutral1_50" />
+    <public name="system_neutral1_100" />
+    <public name="system_neutral1_200" />
+    <public name="system_neutral1_300" />
+    <public name="system_neutral1_400" />
+    <public name="system_neutral1_500" />
+    <public name="system_neutral1_600" />
+    <public name="system_neutral1_700" />
+    <public name="system_neutral1_800" />
+    <public name="system_neutral1_900" />
+    <public name="system_neutral1_1000" />
+    <public name="system_neutral2_0" />
+    <public name="system_neutral2_50" />
+    <public name="system_neutral2_100" />
+    <public name="system_neutral2_200" />
+    <public name="system_neutral2_300" />
+    <public name="system_neutral2_400" />
+    <public name="system_neutral2_500" />
+    <public name="system_neutral2_600" />
+    <public name="system_neutral2_700" />
+    <public name="system_neutral2_800" />
+    <public name="system_neutral2_900" />
+    <public name="system_neutral2_1000" />
+    <!-- Accent colors, for buttons and UI decorations -->
+    <public name="system_accent1_0" />
+    <public name="system_accent1_50" />
+    <public name="system_accent1_100" />
+    <public name="system_accent1_200" />
+    <public name="system_accent1_300" />
+    <public name="system_accent1_400" />
+    <public name="system_accent1_500" />
+    <public name="system_accent1_600" />
+    <public name="system_accent1_700" />
+    <public name="system_accent1_800" />
+    <public name="system_accent1_900" />
+    <public name="system_accent1_1000" />
+    <public name="system_accent2_0" />
+    <public name="system_accent2_50" />
+    <public name="system_accent2_100" />
+    <public name="system_accent2_200" />
+    <public name="system_accent2_300" />
+    <public name="system_accent2_400" />
+    <public name="system_accent2_500" />
+    <public name="system_accent2_600" />
+    <public name="system_accent2_700" />
+    <public name="system_accent2_800" />
+    <public name="system_accent2_900" />
+    <public name="system_accent2_1000" />
+    <public name="system_accent3_0" />
+    <public name="system_accent3_50" />
+    <public name="system_accent3_100" />
+    <public name="system_accent3_200" />
+    <public name="system_accent3_300" />
+    <public name="system_accent3_400" />
+    <public name="system_accent3_500" />
+    <public name="system_accent3_600" />
+    <public name="system_accent3_700" />
+    <public name="system_accent3_800" />
+    <public name="system_accent3_900" />
+    <public name="system_accent3_1000" />
   </public-group>
 
   <public-group type="dimen" first-id="0x01050008">
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index c7ded0c..fbf67e0 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1481,17 +1481,6 @@
         <item name="android:windowExitAnimation">@anim/slide_out_down</item>
     </style>
 
-    <!-- The style for the container of media actions in a notification. -->
-    <!-- @hide -->
-    <style name="NotificationMediaActionContainer">
-        <item name="layout_width">wrap_content</item>
-        <item name="layout_height">wrap_content</item>
-        <item name="layout_marginTop">-21dp</item>
-        <item name="paddingStart">8dp</item>
-        <item name="paddingBottom">@dimen/media_notification_actions_padding_bottom</item>
-        <item name="gravity">top</item>
-    </style>
-
     <!-- The style for normal action button on notification -->
     <style name="NotificationAction" parent="Widget.Material.Light.Button.Borderless.Small">
       <item name="textColor">@color/notification_action_button_text_color</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1d74d85..d6a6f4d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -200,12 +200,7 @@
   <java-symbol type="id" name="action2" />
   <java-symbol type="id" name="action3" />
   <java-symbol type="id" name="action4" />
-  <java-symbol type="id" name="notification_media_seekbar_container" />
   <java-symbol type="id" name="notification_media_content" />
-  <java-symbol type="id" name="notification_media_progress" />
-  <java-symbol type="id" name="notification_media_progress_bar" />
-  <java-symbol type="id" name="notification_media_elapsed_time" />
-  <java-symbol type="id" name="notification_media_total_time" />
   <java-symbol type="id" name="big_picture" />
   <java-symbol type="id" name="big_text" />
   <java-symbol type="id" name="chronometer" />
@@ -525,7 +520,6 @@
   <java-symbol type="dimen" name="notification_top_pad_narrow" />
   <java-symbol type="dimen" name="notification_top_pad_large_text" />
   <java-symbol type="dimen" name="notification_top_pad_large_text_narrow" />
-  <java-symbol type="dimen" name="notification_large_icon_circle_padding" />
   <java-symbol type="dimen" name="notification_badge_size" />
   <java-symbol type="dimen" name="immersive_mode_cling_width" />
   <java-symbol type="dimen" name="accessibility_magnification_indicator_width" />
@@ -1564,7 +1558,6 @@
   <java-symbol type="layout" name="immersive_mode_cling" />
   <java-symbol type="layout" name="user_switching_dialog" />
   <java-symbol type="layout" name="common_tab_settings" />
-  <java-symbol type="layout" name="notification_material_media_seekbar" />
   <java-symbol type="layout" name="resolver_list_per_profile" />
   <java-symbol type="layout" name="chooser_list_per_profile" />
   <java-symbol type="layout" name="resolver_empty_states" />
@@ -2921,6 +2914,7 @@
   <java-symbol type="drawable" name="ic_expand_bundle" />
   <java-symbol type="drawable" name="ic_collapse_bundle" />
   <java-symbol type="dimen" name="notification_header_shrink_min_width" />
+  <java-symbol type="dimen" name="notification_header_shrink_hide_width" />
   <java-symbol type="dimen" name="notification_content_margin_start" />
   <java-symbol type="dimen" name="notification_content_margin_end" />
   <java-symbol type="dimen" name="notification_heading_margin_end" />
@@ -3010,7 +3004,6 @@
   <java-symbol type="string" name="new_sms_notification_content" />
 
   <java-symbol type="dimen" name="media_notification_expanded_image_margin_bottom" />
-  <java-symbol type="dimen" name="notification_content_image_margin_end" />
 
   <java-symbol type="bool" name="config_strongAuthRequiredOnBoot" />
 
@@ -3019,8 +3012,6 @@
 
   <java-symbol type="id" name="aerr_wait" />
 
-  <java-symbol type="id" name="notification_content_container" />
-
   <java-symbol type="plurals" name="duration_minutes_shortest" />
   <java-symbol type="plurals" name="duration_hours_shortest" />
   <java-symbol type="plurals" name="duration_days_shortest" />
@@ -3138,7 +3129,6 @@
 
   <java-symbol type="bool" name="config_supportPreRebootSecurityLogs" />
 
-  <java-symbol type="dimen" name="notification_media_image_margin_end" />
   <java-symbol type="id" name="notification_action_list_margin_target" />
   <java-symbol type="dimen" name="notification_action_disabled_alpha" />
   <java-symbol type="id" name="tag_margin_end_when_icon_visible" />
@@ -3461,17 +3451,14 @@
 
   <java-symbol type="dimen" name="notification_big_picture_max_height"/>
   <java-symbol type="dimen" name="notification_big_picture_max_width"/>
-  <java-symbol type="dimen" name="notification_media_image_max_width"/>
-  <java-symbol type="dimen" name="notification_media_image_max_height"/>
   <java-symbol type="dimen" name="notification_right_icon_size"/>
+  <java-symbol type="dimen" name="notification_right_icon_content_margin"/>
   <java-symbol type="dimen" name="notification_actions_icon_drawable_size"/>
   <java-symbol type="dimen" name="notification_custom_view_max_image_height"/>
   <java-symbol type="dimen" name="notification_custom_view_max_image_width"/>
 
   <java-symbol type="dimen" name="notification_big_picture_max_height_low_ram"/>
   <java-symbol type="dimen" name="notification_big_picture_max_width_low_ram"/>
-  <java-symbol type="dimen" name="notification_media_image_max_width_low_ram"/>
-  <java-symbol type="dimen" name="notification_media_image_max_height_low_ram"/>
   <java-symbol type="dimen" name="notification_right_icon_size_low_ram"/>
   <java-symbol type="dimen" name="notification_grayscale_icon_max_size"/>
   <java-symbol type="dimen" name="notification_custom_view_max_image_height_low_ram"/>
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 252938a..0ea6364 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -16,8 +16,6 @@
 
 package android.app;
 
-import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotSame;
@@ -99,23 +97,6 @@
     }
 
     @Test
-    public void testColorSatisfiedWhenBgDarkTextDarker() {
-        Notification.Builder builder = getMediaNotification();
-        Notification n = builder.build();
-
-        assertTrue(n.isColorized());
-
-        // An initial guess where the foreground color is actually darker than an already dark bg
-        int backgroundColor = 0xff585868;
-        int initialForegroundColor = 0xff505868;
-        builder.setColorPalette(backgroundColor, initialForegroundColor);
-        int primaryTextColor = builder.getPrimaryTextColor(builder.mParams);
-        assertTrue(satisfiesTextContrast(primaryTextColor, backgroundColor));
-        int secondaryTextColor = builder.getSecondaryTextColor(builder.mParams);
-        assertTrue(satisfiesTextContrast(secondaryTextColor, backgroundColor));
-    }
-
-    @Test
     public void testHasCompletedProgress_noProgress() {
         Notification n = new Notification.Builder(mContext).build();
 
diff --git a/core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java b/core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java
new file mode 100644
index 0000000..09985a8
--- /dev/null
+++ b/core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.content;
+
+import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.WindowConfiguration;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ *  Build/Install/Run:
+ *   atest FrameworksCoreTests:ComponentCallbacksControllerTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ComponentCallbacksControllerTest {
+    private ComponentCallbacksController mController;
+
+    @Before
+    public void setUp() {
+        mController = new ComponentCallbacksController();
+    }
+
+    @Test
+    public void testUnregisterCallbackWithoutRegistrationNoCrash() {
+        mController.unregisterCallbacks(new FakeComponentCallbacks());
+    }
+
+    @Test
+    public void testDispatchWithEmptyCallbacksNoCrash() {
+        mController.dispatchConfigurationChanged(new Configuration());
+        mController.dispatchLowMemory();
+        mController.dispatchTrimMemory(TRIM_MEMORY_BACKGROUND);
+    }
+
+    @Test
+    public void testClearCallbacksNoCrash() {
+        mController.clearCallbacks();
+    }
+
+    @Test
+    public void testDispatchTrimMemoryWithoutComponentCallbacks2NoCrash() {
+        // Register a ComponentCallbacks instead of ComponentCallbacks2
+        mController.registerCallbacks(new FakeComponentCallbacks());
+
+        mController.dispatchTrimMemory(TRIM_MEMORY_BACKGROUND);
+    }
+
+    @Test
+    public void testDispatchConfigurationChanged() throws Exception {
+        final TestComponentCallbacks2 callback = new TestComponentCallbacks2();
+        mController.registerCallbacks(callback);
+
+        final Configuration config = new Configuration();
+        config.windowConfiguration.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
+        config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100));
+
+        mController.dispatchConfigurationChanged(config);
+
+        assertThat(callback.mConfiguration).isEqualTo(config);
+
+        mController.dispatchConfigurationChanged(Configuration.EMPTY);
+
+        assertThat(callback.mConfiguration).isEqualTo(Configuration.EMPTY);
+    }
+
+    @Test
+    public void testDispatchLowMemory() {
+        final TestComponentCallbacks2 callback = new TestComponentCallbacks2();
+        mController.registerCallbacks(callback);
+
+        mController.dispatchLowMemory();
+
+        assertThat(callback.mLowMemoryCalled).isTrue();
+    }
+
+    @Test
+    public void testDispatchTrimMemory() {
+        final TestComponentCallbacks2 callback = new TestComponentCallbacks2();
+        mController.registerCallbacks(callback);
+
+        mController.dispatchTrimMemory(TRIM_MEMORY_BACKGROUND);
+
+        assertThat(callback.mLevel).isEqualTo(TRIM_MEMORY_BACKGROUND);
+    }
+
+    private static class FakeComponentCallbacks implements ComponentCallbacks {
+        @Override
+        public void onConfigurationChanged(@NonNull Configuration newConfig) {}
+
+        @Override
+        public void onLowMemory() {}
+    }
+
+    private static class TestComponentCallbacks2 implements ComponentCallbacks2 {
+        private Configuration mConfiguration;
+        private boolean mLowMemoryCalled;
+        private int mLevel;
+
+        @Override
+        public void onConfigurationChanged(@NonNull Configuration newConfig) {
+            mConfiguration = newConfig;
+        }
+
+        @Override
+        public void onLowMemory() {
+            mLowMemoryCalled = true;
+        }
+
+        @Override
+        public void onTrimMemory(int level) {
+            mLevel = level;
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
index 11239db..30b2d8e 100644
--- a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
@@ -28,13 +28,15 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 
 @Presubmit
 @RunWith(JUnit4.class)
 public class CombinedVibrationEffectTest {
     private static final VibrationEffect VALID_EFFECT = VibrationEffect.createOneShot(10, 255);
-    private static final VibrationEffect INVALID_EFFECT = new VibrationEffect.OneShot(-1, -1);
+    private static final VibrationEffect INVALID_EFFECT = new VibrationEffect.Composed(
+            new ArrayList<>(), 0);
 
     @Test
     public void testValidateMono() {
diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java
index d555cd9..242adab 100644
--- a/core/tests/coretests/src/android/os/VibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java
@@ -18,13 +18,9 @@
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNotSame;
 import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertSame;
 import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
 
-import static org.junit.Assert.assertArrayEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -35,11 +31,13 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.Uri;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
 import android.platform.test.annotations.Presubmit;
 
 import com.android.internal.R;
 
-import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.junit.MockitoJUnitRunner;
@@ -53,9 +51,6 @@
     private static final String RINGTONE_URI_3 = "content://test/system/ringtone_3";
     private static final String UNKNOWN_URI = "content://test/system/other_audio";
 
-    private static final float INTENSITY_SCALE_TOLERANCE = 1e-2f;
-    private static final int AMPLITUDE_SCALE_TOLERANCE = 1;
-
     private static final long TEST_TIMING = 100;
     private static final int TEST_AMPLITUDE = 100;
     private static final long[] TEST_TIMINGS = new long[] { 100, 100, 200 };
@@ -68,12 +63,6 @@
             VibrationEffect.createOneShot(TEST_TIMING, VibrationEffect.DEFAULT_AMPLITUDE);
     private static final VibrationEffect TEST_WAVEFORM =
             VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1);
-    private static final VibrationEffect TEST_COMPOSED =
-            VibrationEffect.startComposition()
-                    .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 1)
-                    .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
-                    .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0f, 100)
-                    .compose();
 
     @Test
     public void getRingtones_noPrebakedRingtones() {
@@ -169,250 +158,106 @@
     }
 
     @Test
-    public void testScalePrebaked_scalesFallbackEffect() {
-        VibrationEffect.Prebaked prebaked =
-                (VibrationEffect.Prebaked) VibrationEffect.get(VibrationEffect.RINGTONES[1]);
-        assertSame(prebaked, prebaked.scale(0.5f));
-
-        prebaked = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
-                VibrationEffect.EFFECT_STRENGTH_MEDIUM, TEST_ONE_SHOT);
-        VibrationEffect.OneShot scaledFallback =
-                (VibrationEffect.OneShot) prebaked.scale(0.5f).getFallbackEffect();
-        assertEquals(34, scaledFallback.getAmplitude(), AMPLITUDE_SCALE_TOLERANCE);
-    }
-
-    @Test
-    public void testResolvePrebaked_resolvesFallbackEffectIfSet() {
-        VibrationEffect.Prebaked prebaked =
-                (VibrationEffect.Prebaked) VibrationEffect.get(VibrationEffect.RINGTONES[1]);
-        assertSame(prebaked, prebaked.resolve(1000));
-
-        prebaked = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
-                VibrationEffect.EFFECT_STRENGTH_MEDIUM,
-                VibrationEffect.createOneShot(1, VibrationEffect.DEFAULT_AMPLITUDE));
-        VibrationEffect.OneShot resolvedFallback =
-                (VibrationEffect.OneShot) prebaked.resolve(10).getFallbackEffect();
-        assertEquals(10, resolvedFallback.getAmplitude());
-    }
-
-    @Test
-    public void testScaleOneShot() {
-        VibrationEffect.OneShot unset = new VibrationEffect.OneShot(
-                TEST_TIMING, VibrationEffect.DEFAULT_AMPLITUDE);
-        assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, unset.scale(2).getAmplitude());
-
-        VibrationEffect.OneShot initial = (VibrationEffect.OneShot) TEST_ONE_SHOT;
-
-        VibrationEffect.OneShot halved = initial.scale(0.5f);
-        assertEquals(34, halved.getAmplitude(), AMPLITUDE_SCALE_TOLERANCE);
-
-        VibrationEffect.OneShot copied = initial.scale(1f);
-        assertEquals(TEST_AMPLITUDE, copied.getAmplitude());
-
-        VibrationEffect.OneShot scaledUp = initial.scale(1.5f);
-        assertTrue(scaledUp.getAmplitude() > initial.getAmplitude());
-        VibrationEffect.OneShot restored = scaledUp.scale(2 / 3f);
-        // Does not restore to the exact original value because scale up is a bit offset.
-        assertEquals(105, restored.getAmplitude(), AMPLITUDE_SCALE_TOLERANCE);
-
-        VibrationEffect.OneShot scaledDown = initial.scale(0.8f);
-        assertTrue(scaledDown.getAmplitude() < initial.getAmplitude());
-        restored = scaledDown.scale(1.25f);
-        // Does not restore to the exact original value because scale up is a bit offset.
-        assertEquals(101, restored.getAmplitude(), AMPLITUDE_SCALE_TOLERANCE);
-
-        // Does not go below min amplitude while scaling down.
-        VibrationEffect.OneShot minAmplitude = new VibrationEffect.OneShot(TEST_TIMING, 1);
-        assertEquals(1, minAmplitude.scale(0.5f).getAmplitude());
-    }
-
-    @Test
     public void testResolveOneShot() {
-        VibrationEffect.OneShot initial = (VibrationEffect.OneShot) DEFAULT_ONE_SHOT;
-        VibrationEffect.OneShot resolved = initial.resolve(239);
-        assertNotSame(initial, resolved);
-        assertEquals(239, resolved.getAmplitude());
+        VibrationEffect.Composed resolved = DEFAULT_ONE_SHOT.resolve(51);
+        assertEquals(0.2f, ((StepSegment) resolved.getSegments().get(0)).getAmplitude());
 
-        // Ignores input when amplitude already set.
-        VibrationEffect.OneShot resolved2 = resolved.resolve(10);
-        assertSame(resolved, resolved2);
-        assertEquals(239, resolved2.getAmplitude());
-    }
-
-    @Test
-    public void testResolveOneshotFailsWhenMaxAmplitudeAboveThreshold() {
-        try {
-            TEST_ONE_SHOT.resolve(1000);
-            fail("Max amplitude above threshold, should throw IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-        }
-    }
-
-    @Test
-    public void testResolveOneshotFailsWhenAmplitudeNonPositive() {
-        try {
-            TEST_ONE_SHOT.resolve(0);
-            fail("Amplitude is set to zero, should throw IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-        }
-    }
-
-    @Test
-    public void testScaleWaveform() {
-        VibrationEffect.Waveform initial = (VibrationEffect.Waveform) TEST_WAVEFORM;
-
-        VibrationEffect.Waveform copied = initial.scale(1f);
-        assertArrayEquals(TEST_AMPLITUDES, copied.getAmplitudes());
-
-        VibrationEffect.Waveform scaled = initial.scale(0.9f);
-        assertEquals(216, scaled.getAmplitudes()[0], AMPLITUDE_SCALE_TOLERANCE);
-        assertEquals(0, scaled.getAmplitudes()[1]);
-        assertEquals(-1, scaled.getAmplitudes()[2]);
-
-        VibrationEffect.Waveform minAmplitude = new VibrationEffect.Waveform(
-                new long[]{100}, new int[] {1}, -1);
-        assertArrayEquals(new int[]{1}, minAmplitude.scale(0.5f).getAmplitudes());
+        assertThrows(IllegalArgumentException.class, () -> DEFAULT_ONE_SHOT.resolve(1000));
     }
 
     @Test
     public void testResolveWaveform() {
-        VibrationEffect.Waveform initial = (VibrationEffect.Waveform) TEST_WAVEFORM;
-        VibrationEffect.Waveform resolved = initial.resolve(123);
-        assertNotSame(initial, resolved);
-        assertArrayEquals(new int[]{255, 0, 123}, resolved.getAmplitudes());
+        VibrationEffect.Composed resolved = TEST_WAVEFORM.resolve(102);
+        assertEquals(0.4f, ((StepSegment) resolved.getSegments().get(2)).getAmplitude());
 
-        // Ignores input when amplitude already set.
-        VibrationEffect.Waveform resolved2 = resolved.resolve(10);
-        assertSame(resolved, resolved2);
-        assertArrayEquals(new int[]{255, 0, 123}, resolved2.getAmplitudes());
+        assertThrows(IllegalArgumentException.class, () -> TEST_WAVEFORM.resolve(1000));
     }
 
     @Test
-    public void testResolveWaveformFailsWhenMaxAmplitudeAboveThreshold() {
-        try {
-            TEST_WAVEFORM.resolve(1000);
-            fail("Max amplitude above threshold, should throw IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-        }
+    public void testResolvePrebaked() {
+        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+        assertEquals(effect, effect.resolve(51));
+    }
+
+    @Test
+    public void testResolveComposed() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 1)
+                .compose();
+        assertEquals(effect, effect.resolve(51));
+    }
+
+    @Test
+    public void testApplyEffectStrengthOneShot() {
+        VibrationEffect.Composed applied = DEFAULT_ONE_SHOT.applyEffectStrength(
+                VibrationEffect.EFFECT_STRENGTH_LIGHT);
+        assertEquals(DEFAULT_ONE_SHOT, applied);
+    }
+
+    @Test
+    public void testApplyEffectStrengthWaveform() {
+        VibrationEffect.Composed applied = TEST_WAVEFORM.applyEffectStrength(
+                VibrationEffect.EFFECT_STRENGTH_LIGHT);
+        assertEquals(TEST_WAVEFORM, applied);
+    }
+
+    @Test
+    public void testApplyEffectStrengthPrebaked() {
+        VibrationEffect.Composed applied = VibrationEffect.get(VibrationEffect.EFFECT_CLICK)
+                .applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT);
+        assertEquals(VibrationEffect.EFFECT_STRENGTH_LIGHT,
+                ((PrebakedSegment) applied.getSegments().get(0)).getEffectStrength());
+    }
+
+    @Test
+    public void testApplyEffectStrengthComposed() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1)
+                .compose();
+        assertEquals(effect, effect.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT));
+    }
+
+    @Test
+    public void testScaleOneShot() {
+        VibrationEffect.Composed scaledUp = TEST_ONE_SHOT.scale(1.5f);
+        assertTrue(100 / 255f < ((StepSegment) scaledUp.getSegments().get(0)).getAmplitude());
+
+        VibrationEffect.Composed scaledDown = TEST_ONE_SHOT.scale(0.5f);
+        assertTrue(100 / 255f > ((StepSegment) scaledDown.getSegments().get(0)).getAmplitude());
+    }
+
+    @Test
+    public void testScaleWaveform() {
+        VibrationEffect.Composed scaledUp = TEST_WAVEFORM.scale(1.5f);
+        assertEquals(1f, ((StepSegment) scaledUp.getSegments().get(0)).getAmplitude(), 1e-5f);
+
+        VibrationEffect.Composed scaledDown = TEST_WAVEFORM.scale(0.5f);
+        assertTrue(1f > ((StepSegment) scaledDown.getSegments().get(0)).getAmplitude());
+    }
+
+    @Test
+    public void testScalePrebaked() {
+        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+
+        VibrationEffect.Composed scaledUp = effect.scale(1.5f);
+        assertEquals(effect, scaledUp);
+
+        VibrationEffect.Composed scaledDown = effect.scale(0.5f);
+        assertEquals(effect, scaledDown);
     }
 
     @Test
     public void testScaleComposed() {
-        VibrationEffect.Composed initial = (VibrationEffect.Composed) TEST_COMPOSED;
-
-        VibrationEffect.Composed copied = initial.scale(1);
-        assertEquals(1f, copied.getPrimitiveEffects().get(0).scale);
-        assertEquals(0.5f, copied.getPrimitiveEffects().get(1).scale);
-        assertEquals(0f, copied.getPrimitiveEffects().get(2).scale);
-
-        VibrationEffect.Composed halved = initial.scale(0.5f);
-        assertEquals(0.34f, halved.getPrimitiveEffects().get(0).scale, INTENSITY_SCALE_TOLERANCE);
-        assertEquals(0.17f, halved.getPrimitiveEffects().get(1).scale, INTENSITY_SCALE_TOLERANCE);
-        assertEquals(0f, halved.getPrimitiveEffects().get(2).scale);
-
-        VibrationEffect.Composed scaledUp = initial.scale(1.5f);
-        // Does not scale up from 1.
-        assertEquals(1f, scaledUp.getPrimitiveEffects().get(0).scale, INTENSITY_SCALE_TOLERANCE);
-        assertTrue(0.5f < scaledUp.getPrimitiveEffects().get(1).scale);
-        assertEquals(0f, scaledUp.getPrimitiveEffects().get(2).scale);
-
-        VibrationEffect.Composed restored = scaledUp.scale(2 / 3f);
-        // The original value was not scaled up, so this only scales it down.
-        assertEquals(0.53f, restored.getPrimitiveEffects().get(0).scale, INTENSITY_SCALE_TOLERANCE);
-        // Does not restore to the exact original value because scale up is a bit offset.
-        assertEquals(0.47f, restored.getPrimitiveEffects().get(1).scale, INTENSITY_SCALE_TOLERANCE);
-        assertEquals(0f, restored.getPrimitiveEffects().get(2).scale);
-
-        VibrationEffect.Composed scaledDown = initial.scale(0.8f);
-        assertTrue(1f > scaledDown.getPrimitiveEffects().get(0).scale);
-        assertTrue(0.5f > scaledDown.getPrimitiveEffects().get(1).scale);
-        assertEquals(0f, scaledDown.getPrimitiveEffects().get(2).scale);
-
-        restored = scaledDown.scale(1.25f);
-        // Does not restore to the exact original value because scale up is a bit offset.
-        assertEquals(0.84f, restored.getPrimitiveEffects().get(0).scale, INTENSITY_SCALE_TOLERANCE);
-        assertEquals(0.5f, restored.getPrimitiveEffects().get(1).scale, INTENSITY_SCALE_TOLERANCE);
-        assertEquals(0f, restored.getPrimitiveEffects().get(2).scale);
-    }
-
-    @Test
-    public void testResolveComposed_ignoresDefaultAmplitudeAndReturnsSameEffect() {
-        VibrationEffect initial = TEST_COMPOSED;
-        assertSame(initial, initial.resolve(1000));
-    }
-
-    @Test
-    public void testScaleAppliesSameAdjustmentsOnAllEffects() {
-        VibrationEffect.OneShot oneShot = new VibrationEffect.OneShot(TEST_TIMING, TEST_AMPLITUDE);
-        VibrationEffect.Waveform waveform = new VibrationEffect.Waveform(
-                new long[] { TEST_TIMING }, new int[]{ TEST_AMPLITUDE }, -1);
-        VibrationEffect.Composed composed =
+        VibrationEffect.Composed effect =
                 (VibrationEffect.Composed) VibrationEffect.startComposition()
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK,
-                                TEST_AMPLITUDE / 255f)
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1)
                         .compose();
 
-        assertEquals(oneShot.scale(0.8f).getAmplitude(),
-                waveform.scale(0.8f).getAmplitudes()[0],
-                AMPLITUDE_SCALE_TOLERANCE);
-        assertEquals(oneShot.scale(1.2f).getAmplitude() / 255f,
-                composed.scale(1.2f).getPrimitiveEffects().get(0).scale,
-                INTENSITY_SCALE_TOLERANCE);
-    }
+        VibrationEffect.Composed scaledUp = effect.scale(1.5f);
+        assertTrue(0.5f < ((PrimitiveSegment) scaledUp.getSegments().get(0)).getScale());
 
-    @Test
-    public void testScaleOnMaxAmplitude() {
-        VibrationEffect.OneShot oneShot = new VibrationEffect.OneShot(
-                TEST_TIMING, VibrationEffect.MAX_AMPLITUDE);
-        VibrationEffect.Waveform waveform = new VibrationEffect.Waveform(
-                new long[]{TEST_TIMING}, new int[]{VibrationEffect.MAX_AMPLITUDE}, -1);
-        VibrationEffect.Composed composed =
-                (VibrationEffect.Composed) VibrationEffect.startComposition()
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
-                        .compose();
-
-        // Scale up does NOT scale MAX_AMPLITUDE
-        assertEquals(VibrationEffect.MAX_AMPLITUDE, oneShot.scale(1.1f).getAmplitude());
-        assertEquals(VibrationEffect.MAX_AMPLITUDE, waveform.scale(1.2f).getAmplitudes()[0]);
-        assertEquals(1f,
-                composed.scale(1.4f).getPrimitiveEffects().get(0).scale,
-                INTENSITY_SCALE_TOLERANCE); // This needs tolerance for float point comparison.
-
-        // Scale down does scale MAX_AMPLITUDE
-        assertEquals(216, oneShot.scale(0.9f).getAmplitude(), AMPLITUDE_SCALE_TOLERANCE);
-        assertEquals(180, waveform.scale(0.8f).getAmplitudes()[0], AMPLITUDE_SCALE_TOLERANCE);
-        assertEquals(0.57f, composed.scale(0.7f).getPrimitiveEffects().get(0).scale,
-                INTENSITY_SCALE_TOLERANCE);
-    }
-
-    @Test
-    public void getEffectStrength_returnsValueFromConstructor() {
-        VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
-                VibrationEffect.EFFECT_STRENGTH_LIGHT, null);
-        Assert.assertEquals(VibrationEffect.EFFECT_STRENGTH_LIGHT, effect.getEffectStrength());
-    }
-
-    @Test
-    public void getFallbackEffect_withFallbackDisabled_isNull() {
-        VibrationEffect fallback = VibrationEffect.createOneShot(100, 100);
-        VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
-                false, VibrationEffect.EFFECT_STRENGTH_LIGHT);
-        Assert.assertNull(effect.getFallbackEffect());
-    }
-
-    @Test
-    public void getFallbackEffect_withoutEffectSet_isNull() {
-        VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
-                true, VibrationEffect.EFFECT_STRENGTH_LIGHT);
-        Assert.assertNull(effect.getFallbackEffect());
-    }
-
-    @Test
-    public void getFallbackEffect_withFallback_returnsValueFromConstructor() {
-        VibrationEffect fallback = VibrationEffect.createOneShot(100, 100);
-        VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
-                VibrationEffect.EFFECT_STRENGTH_LIGHT, fallback);
-        Assert.assertEquals(fallback, effect.getFallbackEffect());
+        VibrationEffect.Composed scaledDown = effect.scale(0.5f);
+        assertTrue(0.5f > ((PrimitiveSegment) scaledDown.getSegments().get(0)).getScale());
     }
 
     private Resources mockRingtoneResources() {
diff --git a/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java
new file mode 100644
index 0000000..de80812
--- /dev/null
+++ b/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.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 android.os.vibrator;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertSame;
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertNotEquals;
+import static org.testng.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.os.VibrationEffect;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class PrebakedSegmentTest {
+
+    @Test
+    public void testCreation() {
+        PrebakedSegment prebaked = new PrebakedSegment(
+                VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+
+        assertEquals(-1, prebaked.getDuration());
+        assertTrue(prebaked.hasNonZeroAmplitude());
+        assertEquals(VibrationEffect.EFFECT_CLICK, prebaked.getEffectId());
+        assertEquals(VibrationEffect.EFFECT_STRENGTH_MEDIUM, prebaked.getEffectStrength());
+        assertTrue(prebaked.shouldFallback());
+    }
+
+    @Test
+    public void testSerialization() {
+        PrebakedSegment original = new PrebakedSegment(
+                VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+        Parcel parcel = Parcel.obtain();
+        original.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        assertEquals(original, PrebakedSegment.CREATOR.createFromParcel(parcel));
+    }
+
+    @Test
+    public void testValidate() {
+        new PrebakedSegment(VibrationEffect.EFFECT_CLICK, true,
+                VibrationEffect.EFFECT_STRENGTH_MEDIUM).validate();
+
+        assertThrows(IllegalArgumentException.class,
+                () -> new PrebakedSegment(1000, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM)
+                        .validate());
+        assertThrows(IllegalArgumentException.class,
+                () -> new PrebakedSegment(VibrationEffect.EFFECT_TICK, false, 1000)
+                        .validate());
+    }
+
+    @Test
+    public void testResolve_ignoresAndReturnsSameEffect() {
+        PrebakedSegment prebaked = new PrebakedSegment(
+                VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+        assertSame(prebaked, prebaked.resolve(1000));
+    }
+
+    @Test
+    public void testApplyEffectStrength() {
+        PrebakedSegment medium = new PrebakedSegment(
+                VibrationEffect.EFFECT_THUD, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+
+        PrebakedSegment light = medium.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT);
+        assertNotEquals(medium, light);
+        assertEquals(medium.getEffectId(), light.getEffectId());
+        assertEquals(medium.shouldFallback(), light.shouldFallback());
+        assertEquals(VibrationEffect.EFFECT_STRENGTH_LIGHT, light.getEffectStrength());
+
+        PrebakedSegment strong = medium.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG);
+        assertNotEquals(medium, strong);
+        assertEquals(medium.getEffectId(), strong.getEffectId());
+        assertEquals(medium.shouldFallback(), strong.shouldFallback());
+        assertEquals(VibrationEffect.EFFECT_STRENGTH_STRONG, strong.getEffectStrength());
+
+        assertSame(medium, medium.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_MEDIUM));
+        // Invalid vibration effect strength is ignored.
+        assertSame(medium, medium.applyEffectStrength(1000));
+    }
+
+    @Test
+    public void testScale_ignoresAndReturnsSameEffect() {
+        PrebakedSegment prebaked = new PrebakedSegment(
+                VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+        assertSame(prebaked, prebaked.scale(0.5f));
+    }
+}
diff --git a/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java
new file mode 100644
index 0000000..538655b
--- /dev/null
+++ b/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertSame;
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.os.VibrationEffect;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class PrimitiveSegmentTest {
+    private static final float TOLERANCE = 1e-2f;
+
+    @Test
+    public void testCreation() {
+        PrimitiveSegment primitive = new PrimitiveSegment(
+                VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 10);
+
+        assertEquals(-1, primitive.getDuration());
+        assertTrue(primitive.hasNonZeroAmplitude());
+        assertEquals(VibrationEffect.Composition.PRIMITIVE_CLICK, primitive.getPrimitiveId());
+        assertEquals(10, primitive.getDelay());
+        assertEquals(1f, primitive.getScale(), TOLERANCE);
+    }
+
+    @Test
+    public void testSerialization() {
+        PrimitiveSegment original = new PrimitiveSegment(
+                VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 10);
+        Parcel parcel = Parcel.obtain();
+        original.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        assertEquals(original, PrimitiveSegment.CREATOR.createFromParcel(parcel));
+    }
+
+    @Test
+    public void testValidate() {
+        new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0).validate();
+
+        assertThrows(IllegalArgumentException.class,
+                () -> new PrimitiveSegment(1000, 0, 10).validate());
+        assertThrows(IllegalArgumentException.class,
+                () -> new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_NOOP, -1, 0)
+                        .validate());
+        assertThrows(IllegalArgumentException.class,
+                () -> new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_NOOP, 1, -1)
+                        .validate());
+    }
+
+    @Test
+    public void testResolve_ignoresAndReturnsSameEffect() {
+        PrimitiveSegment primitive = new PrimitiveSegment(
+                VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0);
+        assertSame(primitive, primitive.resolve(1000));
+    }
+
+    @Test
+    public void testApplyEffectStrength_ignoresAndReturnsSameEffect() {
+        PrimitiveSegment primitive = new PrimitiveSegment(
+                VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0);
+        assertSame(primitive,
+                primitive.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG));
+    }
+
+    @Test
+    public void testScale_fullPrimitiveScaleValue() {
+        PrimitiveSegment initial = new PrimitiveSegment(
+                VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0);
+
+        assertEquals(1f, initial.scale(1).getScale(), TOLERANCE);
+        assertEquals(0.34f, initial.scale(0.5f).getScale(), TOLERANCE);
+        // The original value was not scaled up, so this only scales it down.
+        assertEquals(1f, initial.scale(1.5f).getScale(), TOLERANCE);
+        assertEquals(0.53f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE);
+        // Does not restore to the exact original value because scale up is a bit offset.
+        assertEquals(0.71f, initial.scale(0.8f).getScale(), TOLERANCE);
+        assertEquals(0.84f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE);
+    }
+
+    @Test
+    public void testScale_halfPrimitiveScaleValue() {
+        PrimitiveSegment initial = new PrimitiveSegment(
+                VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 0);
+
+        assertEquals(0.5f, initial.scale(1).getScale(), TOLERANCE);
+        assertEquals(0.17f, initial.scale(0.5f).getScale(), TOLERANCE);
+        // The original value was not scaled up, so this only scales it down.
+        assertEquals(0.86f, initial.scale(1.5f).getScale(), TOLERANCE);
+        assertEquals(0.47f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE);
+        // Does not restore to the exact original value because scale up is a bit offset.
+        assertEquals(0.35f, initial.scale(0.8f).getScale(), TOLERANCE);
+        assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE);
+    }
+
+    @Test
+    public void testScale_zeroPrimitiveScaleValue() {
+        PrimitiveSegment initial = new PrimitiveSegment(
+                VibrationEffect.Composition.PRIMITIVE_CLICK, 0, 0);
+
+        assertEquals(0f, initial.scale(1).getScale(), TOLERANCE);
+        assertEquals(0f, initial.scale(0.5f).getScale(), TOLERANCE);
+        assertEquals(0f, initial.scale(1.5f).getScale(), TOLERANCE);
+        assertEquals(0f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE);
+        assertEquals(0f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE);
+    }
+}
diff --git a/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java
new file mode 100644
index 0000000..188b6c1
--- /dev/null
+++ b/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertSame;
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.os.VibrationEffect;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class StepSegmentTest {
+    private static final float TOLERANCE = 1e-2f;
+
+    @Test
+    public void testCreation() {
+        StepSegment step = new StepSegment(/* amplitude= */ 1f, /* duration= */ 100);
+
+        assertEquals(100, step.getDuration());
+        assertTrue(step.hasNonZeroAmplitude());
+        assertEquals(1f, step.getAmplitude());
+    }
+
+    @Test
+    public void testSerialization() {
+        StepSegment original = new StepSegment(0.5f, 10);
+        Parcel parcel = Parcel.obtain();
+        original.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        assertEquals(original, StepSegment.CREATOR.createFromParcel(parcel));
+    }
+
+    @Test
+    public void testValidate() {
+        new StepSegment(/* amplitude= */ 0f, /* duration= */ 100).validate();
+
+        assertThrows(IllegalArgumentException.class,
+                () -> new StepSegment(/* amplitude= */ -2, 10).validate());
+        assertThrows(IllegalArgumentException.class,
+                () -> new StepSegment(/* amplitude= */ 2, 10).validate());
+        assertThrows(IllegalArgumentException.class,
+                () -> new StepSegment(2, /* duration= */ -1).validate());
+    }
+
+    @Test
+    public void testHasNonZeroAmplitude() {
+        assertTrue(new StepSegment(1f, 0).hasNonZeroAmplitude());
+        assertTrue(new StepSegment(0.01f, 0).hasNonZeroAmplitude());
+        assertTrue(new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0).hasNonZeroAmplitude());
+        assertFalse(new StepSegment(0, 0).hasNonZeroAmplitude());
+    }
+
+    @Test
+    public void testResolve() {
+        StepSegment original = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0);
+        assertEquals(1f, original.resolve(VibrationEffect.MAX_AMPLITUDE).getAmplitude());
+        assertEquals(0.2f, original.resolve(51).getAmplitude(), TOLERANCE);
+
+        StepSegment resolved = new StepSegment(0, 0);
+        assertSame(resolved, resolved.resolve(100));
+
+        assertThrows(IllegalArgumentException.class, () -> resolved.resolve(1000));
+    }
+
+    @Test
+    public void testApplyEffectStrength_ignoresAndReturnsSameEffect() {
+        StepSegment step = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0);
+        assertSame(step, step.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG));
+    }
+
+    @Test
+    public void testScale_fullAmplitude() {
+        StepSegment initial = new StepSegment(1f, 0);
+
+        assertEquals(1f, initial.scale(1).getAmplitude(), TOLERANCE);
+        assertEquals(0.34f, initial.scale(0.5f).getAmplitude(), TOLERANCE);
+        // The original value was not scaled up, so this only scales it down.
+        assertEquals(1f, initial.scale(1.5f).getAmplitude(), TOLERANCE);
+        assertEquals(0.53f, initial.scale(1.5f).scale(2 / 3f).getAmplitude(), TOLERANCE);
+        // Does not restore to the exact original value because scale up is a bit offset.
+        assertEquals(0.71f, initial.scale(0.8f).getAmplitude(), TOLERANCE);
+        assertEquals(0.84f, initial.scale(0.8f).scale(1.25f).getAmplitude(), TOLERANCE);
+    }
+
+    @Test
+    public void testScale_halfAmplitude() {
+        StepSegment initial = new StepSegment(0.5f, 0);
+
+        assertEquals(0.5f, initial.scale(1).getAmplitude(), TOLERANCE);
+        assertEquals(0.17f, initial.scale(0.5f).getAmplitude(), TOLERANCE);
+        // The original value was not scaled up, so this only scales it down.
+        assertEquals(0.86f, initial.scale(1.5f).getAmplitude(), TOLERANCE);
+        assertEquals(0.47f, initial.scale(1.5f).scale(2 / 3f).getAmplitude(), TOLERANCE);
+        // Does not restore to the exact original value because scale up is a bit offset.
+        assertEquals(0.35f, initial.scale(0.8f).getAmplitude(), TOLERANCE);
+        assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getAmplitude(), TOLERANCE);
+    }
+
+    @Test
+    public void testScale_zeroAmplitude() {
+        StepSegment initial = new StepSegment(0, 0);
+
+        assertEquals(0f, initial.scale(1).getAmplitude(), TOLERANCE);
+        assertEquals(0f, initial.scale(0.5f).getAmplitude(), TOLERANCE);
+        assertEquals(0f, initial.scale(1.5f).getAmplitude(), TOLERANCE);
+    }
+
+    @Test
+    public void testScale_defaultAmplitude() {
+        StepSegment initial = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0);
+
+        assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scale(1).getAmplitude(), TOLERANCE);
+        assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scale(0.5f).getAmplitude(),
+                TOLERANCE);
+        assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scale(1.5f).getAmplitude(),
+                TOLERANCE);
+    }
+}
diff --git a/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java
new file mode 100644
index 0000000..2dd3f69
--- /dev/null
+++ b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.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 android.util;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+/**
+ * Internal tests for {@link SparseDoubleArray}.
+ *
+ * Run using:
+ *  atest FrameworksCoreTests:android.util.SparseDoubleArrayTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SparseDoubleArrayTest {
+    private static final double EXACT_PRECISION = 0;
+    private static final double PRECISION = 0.000000001;
+
+    @Test
+    public void testPutGet() {
+        final SparseDoubleArray sda = new SparseDoubleArray();
+        assertEquals("Array should be empty", 0, sda.size());
+
+        final int[] keys = {1, 6, -14, 53251, 5, -13412, 12, 0, 2};
+        final double[] values = {7, -12.4, 7, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
+                Double.NaN,
+                4311236312.0 / 3431470161413514334123.0,
+                431123636434132151313412.0 / 34323.0,
+                0};
+        for (int i = 0; i < keys.length; i++) {
+            sda.put(keys[i], values[i]);
+        }
+
+        assertEquals("Wrong size array", keys.length, sda.size());
+        // Due to the implementation, we actually expect EXACT double equality.
+        for (int i = 0; i < keys.length; i++) {
+            assertEquals("Wrong value at index " + i, values[i], sda.get(keys[i]), EXACT_PRECISION);
+        }
+
+        // Now check something that was never put in
+        assertEquals("Wrong value for absent index", 0, sda.get(100000), EXACT_PRECISION);
+    }
+
+    @Test
+    public void testAdd() {
+        final SparseDoubleArray sda = new SparseDoubleArray();
+
+        sda.put(4, 6.1);
+        sda.add(4, -1.2);
+        sda.add(2, -1.2);
+
+        assertEquals(6.1 - 1.2, sda.get(4), PRECISION);
+        assertEquals(-1.2, sda.get(2), PRECISION);
+    }
+
+    @Test
+    public void testKeyValueAt() {
+        final SparseDoubleArray sda = new SparseDoubleArray();
+
+        final int[] keys = {1, 6, -14, 53251, 5, -13412, 12, 0, 2};
+        final double[] values = {7, -12.4, 7, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
+                Double.NaN,
+                4311236312.0 / 3431470161413514334123.0,
+                431123636434132151313412.0 / 34323.0,
+                0};
+        for (int i = 0; i < keys.length; i++) {
+            sda.put(keys[i], values[i]);
+        }
+
+        // Sort the sample data.
+        final ArrayMap<Integer, Double> map = new ArrayMap<>(keys.length);
+        for (int i = 0; i < keys.length; i++) {
+            map.put(keys[i], values[i]);
+        }
+        final int[] sortedKeys = Arrays.copyOf(keys, keys.length);
+        Arrays.sort(sortedKeys);
+
+        for (int i = 0; i < sortedKeys.length; i++) {
+            final int expectedKey = sortedKeys[i];
+            final double expectedValue = map.get(expectedKey);
+
+            assertEquals("Wrong key at index " + i, expectedKey, sda.keyAt(i), PRECISION);
+            assertEquals("Wrong value at index " + i, expectedValue, sda.valueAt(i), PRECISION);
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 5de55d7..47556c3 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -98,12 +98,12 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             // test if setVisibility can show IME
             mImeConsumer.onWindowFocusGained();
-            mImeConsumer.applyImeVisibility(true);
+            mController.show(WindowInsets.Type.ime(), true /* fromIme */);
             mController.cancelExistingAnimations();
             assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             // test if setVisibility can hide IME
-            mImeConsumer.applyImeVisibility(false);
+            mController.hide(WindowInsets.Type.ime(), true /* fromIme */);
             mController.cancelExistingAnimations();
             assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
         });
@@ -117,7 +117,7 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             // Request IME visible before control is available.
             mImeConsumer.onWindowFocusGained();
-            mImeConsumer.applyImeVisibility(true /* setVisible */);
+            mController.show(WindowInsets.Type.ime(), true /* fromIme */);
 
             // set control and verify visibility is applied.
             InsetsSourceControl control =
@@ -136,7 +136,7 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             // Request IME visible before control is available.
             mImeConsumer.onWindowFocusGained();
-            mImeConsumer.applyImeVisibility(true /* setVisible */);
+            mController.show(WindowInsets.Type.ime(), true /* fromIme */);
 
             // set control and verify visibility is applied.
             InsetsSourceControl control = Mockito.spy(
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index ff505c4..4390546 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -236,7 +236,7 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained();
             // since there is no focused view, forcefully make IME visible.
-            mController.applyImeVisibility(true /* setVisible */);
+            mController.show(Type.ime(), true /* fromIme */);
             mController.show(Type.all());
             // quickly jump to final state by cancelling it.
             mController.cancelExistingAnimations();
@@ -244,7 +244,7 @@
             assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
             assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
-            mController.applyImeVisibility(false /* setVisible */);
+            mController.hide(Type.ime(), true /* fromIme */);
             mController.hide(Type.all());
             mController.cancelExistingAnimations();
             assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
@@ -261,10 +261,10 @@
         mController.onControlsChanged(new InsetsSourceControl[] { ime });
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained();
-            mController.applyImeVisibility(true);
+            mController.show(Type.ime(), true /* fromIme */);
             mController.cancelExistingAnimations();
             assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
-            mController.applyImeVisibility(false);
+            mController.hide(Type.ime(), true /* fromIme */);
             mController.cancelExistingAnimations();
             assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index d9012f64..4c58ad3 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -154,8 +154,8 @@
         sOverrides.reset();
         sOverrides.createPackageManager = mPackageManagerOverride;
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
-                Boolean.toString(false),
+                SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI,
+                Boolean.toString(true),
                 true /* makeDefault*/);
     }
 
@@ -1017,7 +1017,7 @@
         assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_DEFAULT), is(testBaseScore));
         assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_CHOOSER_TARGET), is(testBaseScore));
         assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE),
-                is(SHORTCUT_TARGET_SCORE_BOOST));
+                is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST));
         assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER),
                 is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST));
     }
@@ -1262,6 +1262,126 @@
                 .getAllValues().get(2).getTaggedData(MetricsEvent.FIELD_RANKED_POSITION), is(0));
     }
 
+    @Test
+    public void testShortcutTargetWithApplyAppLimits() throws InterruptedException {
+        // Set up resources
+        sOverrides.resources = Mockito.spy(
+                InstrumentationRegistry.getInstrumentation().getContext().getResources());
+        when(sOverrides.resources.getInteger(R.integer.config_maxShortcutTargetsPerApp))
+                .thenReturn(1);
+        Intent sendIntent = createSendTextIntent();
+        // We need app targets for direct targets to get displayed
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+        // Create direct share target
+        List<ChooserTarget> serviceTargets = createDirectShareTargets(2,
+                resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
+        ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+
+        // Start activity
+        final ChooserWrapperActivity activity = mActivityRule
+                .launchActivity(Intent.createChooser(sendIntent, null));
+
+        // Insert the direct share target
+        Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>();
+        List<ShareShortcutInfo> shortcutInfos = createShortcuts(activity);
+        directShareToShortcutInfos.put(serviceTargets.get(0),
+                shortcutInfos.get(0).getShortcutInfo());
+        directShareToShortcutInfos.put(serviceTargets.get(1),
+                shortcutInfos.get(1).getShortcutInfo());
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                () -> activity.getAdapter().addServiceResults(
+                        activity.createTestDisplayResolveInfo(sendIntent,
+                                ri,
+                                "testLabel",
+                                "testInfo",
+                                sendIntent,
+                                /* resolveInfoPresentationGetter */ null),
+                        serviceTargets,
+                        TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE,
+                        directShareToShortcutInfos,
+                        List.of())
+        );
+        // Thread.sleep shouldn't be a thing in an integration test but it's
+        // necessary here because of the way the code is structured
+        // TODO: restructure the tests b/129870719
+        Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+
+        assertThat("Chooser should have 3 targets (2 apps, 1 direct)",
+                activity.getAdapter().getCount(), is(3));
+        assertThat("Chooser should have exactly one selectable direct target",
+                activity.getAdapter().getSelectableServiceTargetCount(), is(1));
+        assertThat("The resolver info must match the resolver info used to create the target",
+                activity.getAdapter().getItem(0).getResolveInfo(), is(ri));
+        assertThat("The display label must match",
+                activity.getAdapter().getItem(0).getDisplayLabel(), is("testTitle0"));
+    }
+
+    @Test
+    public void testShortcutTargetWithoutApplyAppLimits() throws InterruptedException {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI,
+                Boolean.toString(false),
+                true /* makeDefault*/);
+        // Set up resources
+        sOverrides.resources = Mockito.spy(
+                InstrumentationRegistry.getInstrumentation().getContext().getResources());
+        when(sOverrides.resources.getInteger(R.integer.config_maxShortcutTargetsPerApp))
+                .thenReturn(1);
+        Intent sendIntent = createSendTextIntent();
+        // We need app targets for direct targets to get displayed
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+        // Create direct share target
+        List<ChooserTarget> serviceTargets = createDirectShareTargets(2,
+                resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
+        ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+
+        // Start activity
+        final ChooserWrapperActivity activity = mActivityRule
+                .launchActivity(Intent.createChooser(sendIntent, null));
+
+        // Insert the direct share target
+        Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>();
+        List<ShareShortcutInfo> shortcutInfos = createShortcuts(activity);
+        directShareToShortcutInfos.put(serviceTargets.get(0),
+                shortcutInfos.get(0).getShortcutInfo());
+        directShareToShortcutInfos.put(serviceTargets.get(1),
+                shortcutInfos.get(1).getShortcutInfo());
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                () -> activity.getAdapter().addServiceResults(
+                        activity.createTestDisplayResolveInfo(sendIntent,
+                                ri,
+                                "testLabel",
+                                "testInfo",
+                                sendIntent,
+                                /* resolveInfoPresentationGetter */ null),
+                        serviceTargets,
+                        TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE,
+                        directShareToShortcutInfos,
+                        List.of())
+        );
+        // Thread.sleep shouldn't be a thing in an integration test but it's
+        // necessary here because of the way the code is structured
+        // TODO: restructure the tests b/129870719
+        Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+
+        assertThat("Chooser should have 4 targets (2 apps, 2 direct)",
+                activity.getAdapter().getCount(), is(4));
+        assertThat("Chooser should have exactly two selectable direct target",
+                activity.getAdapter().getSelectableServiceTargetCount(), is(2));
+        assertThat("The resolver info must match the resolver info used to create the target",
+                activity.getAdapter().getItem(0).getResolveInfo(), is(ri));
+        assertThat("The display label must match",
+                activity.getAdapter().getItem(0).getDisplayLabel(), is("testTitle0"));
+        assertThat("The display label must match",
+                activity.getAdapter().getItem(1).getDisplayLabel(), is("testTitle1"));
+    }
+
     // This test is too long and too slow and should not be taken as an example for future tests.
     @Test
     public void testDirectTargetLoggingWithAppTargetNotRankedPortrait()
diff --git a/data/etc/car/com.android.car.carlauncher.xml b/data/etc/car/com.android.car.carlauncher.xml
index ac16af3..abde232 100644
--- a/data/etc/car/com.android.car.carlauncher.xml
+++ b/data/etc/car/com.android.car.carlauncher.xml
@@ -18,6 +18,7 @@
     <privapp-permissions package="com.android.car.carlauncher">
         <permission name="android.permission.ACTIVITY_EMBEDDING"/>
         <permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
+        <permission name="android.permission.INTERACT_ACROSS_USERS"/>
         <permission name="android.permission.MANAGE_USERS"/>
         <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
         <permission name="android.permission.PACKAGE_USAGE_STATS"/>
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index 3aaf11c..4534d36 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -28,7 +28,7 @@
     public long mNativeObject; // BLASTBufferQueue*
 
     private static native long nativeCreate(String name, long surfaceControl, long width,
-            long height, int format, boolean tripleBufferingEnabled);
+            long height, int format);
     private static native void nativeDestroy(long ptr);
     private static native Surface nativeGetSurface(long ptr, boolean includeSurfaceControlHandle);
     private static native void nativeSetNextTransaction(long ptr, long transactionPtr);
@@ -53,9 +53,8 @@
 
     /** Create a new connection with the surface flinger. */
     public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height,
-            @PixelFormat.Format int format, boolean tripleBufferingEnabled) {
-        mNativeObject = nativeCreate(name, sc.mNativeObject, width, height, format,
-                tripleBufferingEnabled);
+            @PixelFormat.Format int format) {
+        mNativeObject = nativeCreate(name, sc.mNativeObject, width, height, format);
     }
 
     public void destroy() {
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index d2b3cf6..6bd0e0a 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -17,6 +17,8 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.wm.shell">
+    <!-- System permission required by WM Shell Task Organizer. -->
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
     <uses-permission android:name="android.permission.ROTATE_SURFACE_FLINGER" />
     <uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
 </manifest>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
index 4768cf5..fa94ec5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
@@ -126,7 +126,7 @@
      */
     public boolean getSettingsSwipeToNotificationEnabled(ContentResolver resolver) {
         return Settings.Secure.getInt(resolver,
-                Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 1) == 1;
+                Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 0 /* Default OFF */) == 1;
     }
 
     void dump(PrintWriter pw, String prefix, ContentResolver resolver) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 3f46fee..28c8f53 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -16,6 +16,8 @@
 
 package com.android.wm.shell.startingsurface;
 
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+
 import android.annotation.ColorInt;
 import android.annotation.NonNull;
 import android.app.ActivityThread;
@@ -31,6 +33,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
 import android.os.Build;
+import android.os.Trace;
 import android.util.Slog;
 import android.view.SurfaceControl;
 import android.window.SplashScreenView;
@@ -70,6 +73,7 @@
     private int mIconNormalExitDistance;
     private int mIconEarlyExitDistance;
     private final TransactionPool mTransactionPool;
+    private final SplashScreenWindowAttrs mTmpAttrs = new SplashScreenWindowAttrs();
 
     SplashscreenContentDrawer(Context context, int maxAnimatableIconDuration,
             int iconExitAnimDuration, int appRevealAnimDuration, TransactionPool pool) {
@@ -109,49 +113,82 @@
         return new ColorDrawable(getSystemBGColor());
     }
 
-    SplashScreenView makeSplashScreenContentView(Context context, int iconRes,
-            int splashscreenContentResId) {
-        updateDensity();
-        // splash screen content will be deprecated after S.
-        final SplashScreenView ssc =
-                makeSplashscreenContentDrawable(context, splashscreenContentResId);
-
-        if (ssc != null) {
-            return ssc;
-        }
-
-        final SplashScreenWindowAttrs attrs =
-                SplashScreenWindowAttrs.createWindowAttrs(context);
-        final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
+    private @ColorInt int peekWindowBGColor(Context context) {
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "peekWindowBGColor");
         final Drawable themeBGDrawable;
-
-        if (attrs.mWindowBgColor != 0) {
-            themeBGDrawable = new ColorDrawable(attrs.mWindowBgColor);
-        } else if (attrs.mWindowBgResId != 0) {
-            themeBGDrawable = context.getDrawable(attrs.mWindowBgResId);
+        if (mTmpAttrs.mWindowBgColor != 0) {
+            themeBGDrawable = new ColorDrawable(mTmpAttrs.mWindowBgColor);
+        } else if (mTmpAttrs.mWindowBgResId != 0) {
+            themeBGDrawable = context.getDrawable(mTmpAttrs.mWindowBgResId);
         } else {
-            Slog.w(TAG, "Window background not exist!");
             themeBGDrawable = createDefaultBackgroundDrawable();
+            Slog.w(TAG, "Window background does not exist, using " + themeBGDrawable);
         }
+        final int estimatedWindowBGColor = estimateWindowBGColor(themeBGDrawable);
+        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        return estimatedWindowBGColor;
+    }
+
+    private int estimateWindowBGColor(Drawable themeBGDrawable) {
+        final DrawableColorTester themeBGTester =
+                new DrawableColorTester(themeBGDrawable, true /* filterTransparent */);
+        if (themeBGTester.nonTransparentRatio() == 0) {
+            // the window background is transparent, unable to draw
+            Slog.w(TAG, "Window background is transparent, fill background with black color");
+            return getSystemBGColor();
+        } else {
+            return themeBGTester.getDominateColor();
+        }
+    }
+
+    SplashScreenView makeSplashScreenContentView(Context context, int iconRes) {
+        updateDensity();
+
+        getWindowAttrs(context, mTmpAttrs);
+        final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
         final int animationDuration;
         final Drawable iconDrawable;
-        if (attrs.mReplaceIcon != null) {
-            iconDrawable = attrs.mReplaceIcon;
+        if (mTmpAttrs.mReplaceIcon != null) {
+            iconDrawable = mTmpAttrs.mReplaceIcon;
             animationDuration = Math.max(0,
-                    Math.min(attrs.mAnimationDuration, mMaxAnimatableIconDuration));
+                    Math.min(mTmpAttrs.mAnimationDuration, mMaxAnimatableIconDuration));
         } else {
             iconDrawable = iconRes != 0 ? context.getDrawable(iconRes)
                     : context.getPackageManager().getDefaultActivityIcon();
             animationDuration = 0;
         }
+        final int themeBGColor = peekWindowBGColor(context);
         // TODO (b/173975965) Tracking the performance on improved splash screen.
         return builder
                 .setContext(context)
-                .setThemeDrawable(themeBGDrawable)
+                .setWindowBGColor(themeBGColor)
                 .setIconDrawable(iconDrawable)
                 .setIconAnimationDuration(animationDuration)
-                .setBrandingDrawable(attrs.mBrandingImage)
-                .setIconBackground(attrs.mIconBgColor).build();
+                .setBrandingDrawable(mTmpAttrs.mBrandingImage)
+                .setIconBackground(mTmpAttrs.mIconBgColor).build();
+    }
+
+    private static void getWindowAttrs(Context context, SplashScreenWindowAttrs attrs) {
+        final TypedArray typedArray = context.obtainStyledAttributes(
+                com.android.internal.R.styleable.Window);
+        attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
+        attrs.mWindowBgColor = typedArray.getColor(
+                R.styleable.Window_windowSplashScreenBackground, Color.TRANSPARENT);
+        attrs.mReplaceIcon = typedArray.getDrawable(
+                R.styleable.Window_windowSplashScreenAnimatedIcon);
+        attrs.mAnimationDuration = typedArray.getInt(
+                R.styleable.Window_windowSplashScreenAnimationDuration, 0);
+        attrs.mBrandingImage = typedArray.getDrawable(
+                R.styleable.Window_windowSplashScreenBrandingImage);
+        attrs.mIconBgColor = typedArray.getColor(
+                R.styleable.Window_windowSplashScreenIconBackgroundColor, Color.TRANSPARENT);
+        typedArray.recycle();
+        if (DEBUG) {
+            Slog.d(TAG, "window attributes color: "
+                    + Integer.toHexString(attrs.mWindowBgColor)
+                    + " icon " + attrs.mReplaceIcon + " duration " + attrs.mAnimationDuration
+                    + " brandImage " + attrs.mBrandingImage);
+        }
     }
 
     static class SplashScreenWindowAttrs {
@@ -161,35 +198,9 @@
         private Drawable mBrandingImage = null;
         private int mIconBgColor = Color.TRANSPARENT;
         private int mAnimationDuration = 0;
-
-        static SplashScreenWindowAttrs createWindowAttrs(Context context) {
-            final SplashScreenWindowAttrs attrs = new SplashScreenWindowAttrs();
-            final TypedArray typedArray = context.obtainStyledAttributes(
-                    com.android.internal.R.styleable.Window);
-            attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
-            attrs.mWindowBgColor = typedArray.getColor(
-                    R.styleable.Window_windowSplashScreenBackground, Color.TRANSPARENT);
-            attrs.mReplaceIcon = typedArray.getDrawable(
-                    R.styleable.Window_windowSplashScreenAnimatedIcon);
-            attrs.mAnimationDuration = typedArray.getInt(
-                    R.styleable.Window_windowSplashScreenAnimationDuration, 0);
-            attrs.mBrandingImage = typedArray.getDrawable(
-                    R.styleable.Window_windowSplashScreenBrandingImage);
-            attrs.mIconBgColor = typedArray.getColor(
-                    R.styleable.Window_windowSplashScreenIconBackgroundColor, Color.TRANSPARENT);
-            typedArray.recycle();
-            if (DEBUG) {
-                Slog.d(TAG, "window attributes color: "
-                        + Integer.toHexString(attrs.mWindowBgColor)
-                        + " icon " + attrs.mReplaceIcon + " duration " + attrs.mAnimationDuration
-                        + " brandImage " + attrs.mBrandingImage);
-            }
-            return attrs;
-        }
     }
 
     private class StartingWindowViewBuilder {
-        private Drawable mThemeBGDrawable;
         private Drawable mIconDrawable;
         private int mIconAnimationDuration;
         private Context mContext;
@@ -203,8 +214,8 @@
         private Drawable mFinalIconDrawable;
         private float mScale = 1f;
 
-        StartingWindowViewBuilder setThemeDrawable(Drawable background) {
-            mThemeBGDrawable = background;
+        StartingWindowViewBuilder setWindowBGColor(@ColorInt int background) {
+            mThemeColor = background;
             mBuildComplete = false;
             return this;
         }
@@ -247,18 +258,14 @@
                 Slog.e(TAG, "Unable to create StartingWindowView, lack of materials!");
                 return null;
             }
-            if (mThemeBGDrawable == null) {
-                Slog.w(TAG, "Theme Background Drawable is null, forget to set Theme Drawable?");
-                mThemeBGDrawable = createDefaultBackgroundDrawable();
-            }
-            processThemeColor();
+
             if (!processAdaptiveIcon() && mIconDrawable != null) {
                 if (DEBUG) {
                     Slog.d(TAG, "The icon is not an AdaptiveIconDrawable");
                 }
                 mFinalIconDrawable = SplashscreenIconDrawableFactory.makeIconDrawable(
                         mIconBackground != Color.TRANSPARENT
-                        ? mIconBackground : mThemeColor, mIconDrawable);
+                        ? mIconBackground : mThemeColor, mIconDrawable, mIconSize);
             }
             final int iconSize = mFinalIconDrawable != null ? (int) (mIconSize * mScale) : 0;
             mCachedResult = fillViewWithIcon(mContext, iconSize, mFinalIconDrawable);
@@ -266,22 +273,10 @@
             return mCachedResult;
         }
 
-        private void createIconDrawable(Drawable iconDrawable) {
+        private void createIconDrawable(Drawable iconDrawable, int iconSize) {
             mFinalIconDrawable = SplashscreenIconDrawableFactory.makeIconDrawable(
                     mIconBackground != Color.TRANSPARENT
-                    ? mIconBackground : mThemeColor, iconDrawable);
-        }
-
-        private void processThemeColor() {
-            final DrawableColorTester themeBGTester =
-                    new DrawableColorTester(mThemeBGDrawable, true /* filterTransparent */);
-            if (themeBGTester.nonTransparentRatio() == 0) {
-                // the window background is transparent, unable to draw
-                Slog.w(TAG, "Window background is transparent, fill background with black color");
-                mThemeColor = getSystemBGColor();
-            } else {
-                mThemeColor = themeBGTester.getDominateColor();
-            }
+                    ? mIconBackground : mThemeColor, iconDrawable, iconSize);
         }
 
         private boolean processAdaptiveIcon() {
@@ -289,6 +284,7 @@
                 return false;
             }
 
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "processAdaptiveIcon");
             final AdaptiveIconDrawable adaptiveIconDrawable = (AdaptiveIconDrawable) mIconDrawable;
             final DrawableColorTester backIconTester =
                     new DrawableColorTester(adaptiveIconDrawable.getBackground());
@@ -325,29 +321,28 @@
                 if (DEBUG) {
                     Slog.d(TAG, "makeSplashScreenContentView: choose fg icon");
                 }
-                // Using AdaptiveIconDrawable here can help keep the shape consistent with the
-                // current settings.
-                createIconDrawable(iconForeground);
                 // Reference AdaptiveIcon description, outer is 108 and inner is 72, so we
                 // should enlarge the size 108/72 if we only draw adaptiveIcon's foreground.
                 if (foreIconTester.nonTransparentRatio() < ENLARGE_FOREGROUND_ICON_THRESHOLD) {
                     mScale = 1.5f;
                 }
+                // Using AdaptiveIconDrawable here can help keep the shape consistent with the
+                // current settings.
+                final int iconSize = (int) (0.5f + mIconSize * mScale);
+                createIconDrawable(iconForeground, iconSize);
             } else {
                 if (DEBUG) {
                     Slog.d(TAG, "makeSplashScreenContentView: draw whole icon");
                 }
-                if (mIconBackground != Color.TRANSPARENT) {
-                    createIconDrawable(adaptiveIconDrawable);
-                } else {
-                    mFinalIconDrawable = adaptiveIconDrawable;
-                }
+                createIconDrawable(adaptiveIconDrawable, mIconSize);
             }
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
             return true;
         }
 
         private SplashScreenView fillViewWithIcon(Context context,
                 int iconSize, Drawable iconDrawable) {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "fillViewWithIcon");
             final SplashScreenView.Builder builder = new SplashScreenView.Builder(context);
             builder.setIconSize(iconSize).setBackgroundColor(mThemeColor)
                     .setIconBackground(mIconBackground);
@@ -364,6 +359,7 @@
                 Slog.d(TAG, "fillViewWithIcon surfaceWindowView " + splashScreenView);
             }
             splashScreenView.makeSystemUIColorsTransparent();
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
             return splashScreenView;
         }
     }
@@ -395,7 +391,7 @@
         return root < 0.1;
     }
 
-    private static SplashScreenView makeSplashscreenContentDrawable(Context ctx,
+    static SplashScreenView makeSplashscreenContent(Context ctx,
             int splashscreenContentResId) {
         // doesn't support windowSplashscreenContent after S
         // TODO add an allowlist to skip some packages if needed
@@ -525,6 +521,7 @@
                     new TransparentFilterQuantizer();
 
             ComplexDrawableTester(Drawable drawable, boolean filterTransparent) {
+                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "ComplexDrawableTester");
                 final Rect initialBounds = drawable.copyBounds();
                 int width = drawable.getIntrinsicWidth();
                 int height = drawable.getIntrinsicHeight();
@@ -559,6 +556,7 @@
                 }
                 mPalette = builder.generate();
                 bitmap.recycle();
+                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
             }
 
             @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
index 8626dbc..a4a83eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
@@ -16,11 +16,15 @@
 
 package com.android.wm.shell.startingsurface;
 
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+
 import android.animation.Animator;
 import android.animation.ValueAnimator;
 import android.annotation.ColorInt;
 import android.annotation.NonNull;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
 import android.graphics.Matrix;
@@ -28,11 +32,13 @@
 import android.graphics.Path;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.graphics.Shader;
 import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.Animatable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.util.PathParser;
 import android.window.SplashScreenView;
 
@@ -47,12 +53,57 @@
 public class SplashscreenIconDrawableFactory {
 
     static Drawable makeIconDrawable(@ColorInt int backgroundColor,
-            @NonNull Drawable foregroundDrawable) {
+            @NonNull Drawable foregroundDrawable, int iconSize) {
         if (foregroundDrawable instanceof Animatable) {
             return new AnimatableIconDrawable(backgroundColor, foregroundDrawable);
+        } else if (foregroundDrawable instanceof AdaptiveIconDrawable) {
+            return new ImmobileIconDrawable((AdaptiveIconDrawable) foregroundDrawable, iconSize);
         } else {
-            // TODO make a light weight drawable instead of AdaptiveIconDrawable
-            return new AdaptiveIconDrawable(new ColorDrawable(backgroundColor), foregroundDrawable);
+            return new ImmobileIconDrawable(new AdaptiveIconDrawable(
+                    new ColorDrawable(backgroundColor), foregroundDrawable), iconSize);
+        }
+    }
+
+    private static class ImmobileIconDrawable extends Drawable {
+        private Shader mLayersShader;
+        private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG
+                | Paint.FILTER_BITMAP_FLAG);
+
+        ImmobileIconDrawable(AdaptiveIconDrawable drawable, int iconSize) {
+            cachePaint(drawable, iconSize, iconSize);
+        }
+
+        private void cachePaint(AdaptiveIconDrawable drawable, int width, int height) {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "cachePaint");
+            final Bitmap layersBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            final Canvas canvas = new Canvas(layersBitmap);
+            drawable.setBounds(0, 0, width, height);
+            drawable.draw(canvas);
+            mLayersShader = new BitmapShader(layersBitmap, Shader.TileMode.CLAMP,
+                    Shader.TileMode.CLAMP);
+            mPaint.setShader(mLayersShader);
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+            final Rect bounds = getBounds();
+            canvas.drawRect(bounds, mPaint);
+        }
+
+        @Override
+        public void setAlpha(int alpha) {
+
+        }
+
+        @Override
+        public void setColorFilter(ColorFilter colorFilter) {
+
+        }
+
+        @Override
+        public int getOpacity() {
+            return 1;
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index b592121..b500813 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -181,11 +181,7 @@
         }
 
         int windowFlags = 0;
-        final boolean enableHardAccelerated =
-                (activityInfo.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0;
-        if (enableHardAccelerated) {
-            windowFlags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
-        }
+        windowFlags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
 
         final boolean[] showWallpaper = new boolean[1];
         final int[] splashscreenContentResId = new int[1];
@@ -246,8 +242,6 @@
         params.packageName = activityInfo.packageName;
         params.windowAnimations = win.getWindowStyle().getResourceId(
                 com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
-        params.privateFlags |=
-                WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;
         params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         // Setting as trusted overlay to let touches pass through. This is safe because this
         // window is controlled by the system.
@@ -267,21 +261,31 @@
         final int taskId = taskInfo.taskId;
         SplashScreenView sView = null;
         try {
-            sView = mSplashscreenContentDrawer.makeSplashScreenContentView(context, iconRes,
-                            splashscreenContentResId[0]);
             final View view = win.getDecorView();
             final WindowManager wm = mContext.getSystemService(WindowManager.class);
-            if (postAddWindow(taskId, appToken, view, wm, params)) {
+            // splash screen content will be deprecated after S.
+            sView = SplashscreenContentDrawer.makeSplashscreenContent(
+                    context, splashscreenContentResId[0]);
+            final boolean splashscreenContentCompatible = sView != null;
+            if (splashscreenContentCompatible) {
+                win.setContentView(sView);
+            } else {
+                sView = mSplashscreenContentDrawer.makeSplashScreenContentView(context, iconRes);
                 win.setContentView(sView);
                 sView.cacheRootWindow(win);
             }
+            postAddWindow(taskId, appToken, view, wm, params);
         } catch (RuntimeException e) {
             // don't crash if something else bad happens, for example a
             // failure loading resources because we are loading from an app
             // on external storage that has been unmounted.
-            Slog.w(TAG, " failed creating starting window", e);
+            Slog.w(TAG, " failed creating starting window at taskId: " + taskId, e);
+            sView = null;
         } finally {
-            setSplashScreenRecord(taskId, sView);
+            final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
+            if (record != null) {
+                record.setSplashScreenView(sView);
+            }
         }
     }
 
@@ -328,7 +332,7 @@
         ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
     }
 
-    protected boolean postAddWindow(int taskId, IBinder appToken, View view, WindowManager wm,
+    protected void postAddWindow(int taskId, IBinder appToken, View view, WindowManager wm,
             WindowManager.LayoutParams params) {
         boolean shouldSaveView = true;
         try {
@@ -349,7 +353,6 @@
             removeWindowNoAnimate(taskId);
             saveSplashScreenRecord(taskId, view);
         }
-        return shouldSaveView;
     }
 
     private void saveSplashScreenRecord(int taskId, View view) {
@@ -358,13 +361,6 @@
         mStartingWindowRecords.put(taskId, tView);
     }
 
-    private void setSplashScreenRecord(int taskId, SplashScreenView splashScreenView) {
-        final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
-        if (record != null) {
-            record.setSplashScreenView(splashScreenView);
-        }
-    }
-
     private void removeWindowNoAnimate(int taskId) {
         removeWindowSynced(taskId, null, null, false);
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index 207db9e..8ae0a73 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -83,12 +83,11 @@
         }
 
         @Override
-        protected boolean postAddWindow(int taskId, IBinder appToken,
+        protected void postAddWindow(int taskId, IBinder appToken,
                 View view, WindowManager wm, WindowManager.LayoutParams params) {
             // listen for addView
             mAddWindowForTask = taskId;
             mViewThemeResId = view.getContext().getThemeResId();
-            return true;
         }
 
         @Override
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index b71bb07..1455269 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -120,12 +120,6 @@
     int imgHeight = image->height();
     sk_sp<GrDirectContext> grContext = sk_ref_sp(mRenderThread.getGrContext());
 
-    if (bitmap->colorType() == kRGBA_F16_SkColorType &&
-        !grContext->colorTypeSupportedAsSurface(bitmap->colorType())) {
-        ALOGW("Can't copy surface into bitmap, RGBA_F16 config is not supported");
-        return CopyResult::DestinationInvalid;
-    }
-
     CopyResult copyResult = CopyResult::UnknownError;
 
     int displayedWidth = imgWidth, displayedHeight = imgHeight;
@@ -159,12 +153,10 @@
 
 bool Readback::copyLayerInto(Layer* layer, const SkRect* srcRect, const SkRect* dstRect,
                              SkBitmap* bitmap) {
-    /* This intermediate surface is present to work around a bug in SwiftShader that
-     * prevents us from reading the contents of the layer's texture directly. The
-     * workaround involves first rendering that texture into an intermediate buffer and
-     * then reading from the intermediate buffer into the bitmap.
-     * Another reason to render in an offscreen buffer is to scale and to avoid an issue b/62262733
-     * with reading incorrect data from EGLImage backed SkImage (likely a driver bug).
+    /* This intermediate surface is present to work around limitations that LayerDrawable expects
+     * to render into a GPU backed canvas.  Additionally, the offscreen buffer solution works around
+     * a scaling issue (b/62262733) that was encountered when sampling from an EGLImage into a
+     * software buffer.
      */
     sk_sp<SkSurface> tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
                                                               SkBudgeted::kYes, bitmap->info(), 0,
diff --git a/location/java/android/location/provider/ILocationProviderManager.aidl b/location/java/android/location/provider/ILocationProviderManager.aidl
index 50ed046..092ec67f 100644
--- a/location/java/android/location/provider/ILocationProviderManager.aidl
+++ b/location/java/android/location/provider/ILocationProviderManager.aidl
@@ -24,7 +24,7 @@
  * @hide
  */
 interface ILocationProviderManager {
-    void onInitialize(boolean allowed, in ProviderProperties properties, @nullable String packageName, @nullable String attributionTag);
+    void onInitialize(boolean allowed, in ProviderProperties properties, @nullable String attributionTag);
     void onSetAllowed(boolean allowed);
     void onSetProperties(in ProviderProperties properties);
 
diff --git a/location/java/android/location/provider/LocationProviderBase.java b/location/java/android/location/provider/LocationProviderBase.java
index ae6395d..eada22c 100644
--- a/location/java/android/location/provider/LocationProviderBase.java
+++ b/location/java/android/location/provider/LocationProviderBase.java
@@ -96,7 +96,6 @@
             "com.android.location.service.FusedLocationProvider";
 
     private final String mTag;
-    private final @Nullable String mPackageName;
     private final @Nullable String mAttributionTag;
     private final IBinder mBinder;
 
@@ -108,7 +107,6 @@
     public LocationProviderBase(@NonNull Context context, @NonNull String tag,
             @NonNull ProviderProperties properties) {
         mTag = tag;
-        mPackageName = context.getPackageName();
         mAttributionTag = context.getAttributionTag();
         mBinder = new Service();
 
@@ -305,7 +303,7 @@
         public void setLocationProviderManager(ILocationProviderManager manager) {
             synchronized (mBinder) {
                 try {
-                    manager.onInitialize(mAllowed, mProperties, mPackageName, mAttributionTag);
+                    manager.onInitialize(mAllowed, mProperties, mAttributionTag);
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 } catch (RuntimeException e) {
diff --git a/location/java/android/location/util/identity/CallerIdentity.java b/location/java/android/location/util/identity/CallerIdentity.java
index 85a083e..ade0ea4 100644
--- a/location/java/android/location/util/identity/CallerIdentity.java
+++ b/location/java/android/location/util/identity/CallerIdentity.java
@@ -55,6 +55,20 @@
     }
 
     /**
+     * Returns a CallerIdentity with PID and listener ID information stripped. This is mostly
+     * useful for aggregating information for debug purposes, and should not be used in any API with
+     * security requirements.
+     */
+    public static CallerIdentity forAggregation(CallerIdentity callerIdentity) {
+        if (callerIdentity.getPid() == 0 && callerIdentity.getListenerId() == null) {
+            return callerIdentity;
+        }
+
+        return new CallerIdentity(callerIdentity.getUid(), 0, callerIdentity.getPackageName(),
+                callerIdentity.getAttributionTag(), null);
+    }
+
+    /**
      * Creates a CallerIdentity for the current process and context.
      */
     public static CallerIdentity fromContext(Context context) {
@@ -180,17 +194,6 @@
         }
     }
 
-    /**
-     * Returns a CallerIdentity corrosponding to this CallerIdentity but with a null listener id.
-     */
-    public CallerIdentity stripListenerId() {
-        if (mListenerId == null) {
-            return this;
-        } else {
-            return new CallerIdentity(mUid, mPid, mPackageName, mAttributionTag, null);
-        }
-    }
-
     @Override
     public String toString() {
         int length = 10 + mPackageName.length();
diff --git a/location/lib/java/com/android/location/provider/LocationProviderBase.java b/location/lib/java/com/android/location/provider/LocationProviderBase.java
index 7f1cf6d..95f6c2f 100644
--- a/location/lib/java/com/android/location/provider/LocationProviderBase.java
+++ b/location/lib/java/com/android/location/provider/LocationProviderBase.java
@@ -96,7 +96,6 @@
     public static final String FUSED_PROVIDER = LocationManager.FUSED_PROVIDER;
 
     final String mTag;
-    @Nullable final String mPackageName;
     @Nullable final String mAttributionTag;
     final IBinder mBinder;
 
@@ -133,7 +132,6 @@
     public LocationProviderBase(Context context, String tag,
             ProviderPropertiesUnbundled properties) {
         mTag = tag;
-        mPackageName = context != null ? context.getPackageName() : null;
         mAttributionTag = context != null ? context.getAttributionTag() : null;
         mBinder = new Service();
 
@@ -370,7 +368,7 @@
         public void setLocationProviderManager(ILocationProviderManager manager) {
             synchronized (mBinder) {
                 try {
-                    manager.onInitialize(mAllowed, mProperties, mPackageName, mAttributionTag);
+                    manager.onInitialize(mAllowed, mProperties, mAttributionTag);
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 } catch (RuntimeException e) {
diff --git a/media/java/android/media/AudioProfile.java b/media/java/android/media/AudioProfile.java
index 3cd615b..9774e80 100644
--- a/media/java/android/media/AudioProfile.java
+++ b/media/java/android/media/AudioProfile.java
@@ -18,6 +18,9 @@
 
 import android.annotation.NonNull;
 
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
 /**
  * An AudioProfile is specific to an audio format and lists supported sampling rates and
  * channel masks for that format.  An {@link AudioDeviceInfo} has a list of supported AudioProfiles.
@@ -63,4 +66,29 @@
     public @NonNull int[] getSampleRates() {
         return mSamplingRates;
     }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder("{");
+        sb.append(AudioFormat.toLogFriendlyEncoding(mFormat));
+        if (mSamplingRates != null && mSamplingRates.length > 0) {
+            sb.append(", sampling rates=").append(Arrays.toString(mSamplingRates));
+        }
+        if (mChannelMasks != null && mChannelMasks.length > 0) {
+            sb.append(", channel masks=").append(toHexString(mChannelMasks));
+        }
+        if (mChannelIndexMasks != null && mChannelIndexMasks.length > 0) {
+            sb.append(", channel index masks=").append(Arrays.toString(mChannelIndexMasks));
+        }
+        sb.append("}");
+        return sb.toString();
+    }
+
+    private static String toHexString(int[] ints) {
+        if (ints == null || ints.length == 0) {
+            return "";
+        }
+        return Arrays.stream(ints).mapToObj(anInt -> String.format("0x%02X, ", anInt))
+                .collect(Collectors.joining());
+    }
 }
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index cf31e41..a7e2b65 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -1674,9 +1674,11 @@
     public @interface BufferFlag {}
 
     private EventHandler mEventHandler;
+    private EventHandler mOnFirstTunnelFrameReadyHandler;
     private EventHandler mOnFrameRenderedHandler;
     private EventHandler mCallbackHandler;
     private Callback mCallback;
+    private OnFirstTunnelFrameReadyListener mOnFirstTunnelFrameReadyListener;
     private OnFrameRenderedListener mOnFrameRenderedListener;
     private final Object mListenerLock = new Object();
     private MediaCodecInfo mCodecInfo;
@@ -1687,6 +1689,7 @@
     private static final int EVENT_CALLBACK = 1;
     private static final int EVENT_SET_CALLBACK = 2;
     private static final int EVENT_FRAME_RENDERED = 3;
+    private static final int EVENT_FIRST_TUNNEL_FRAME_READY = 4;
 
     private static final int CB_INPUT_AVAILABLE = 1;
     private static final int CB_OUTPUT_AVAILABLE = 2;
@@ -1748,6 +1751,16 @@
                                 mCodec, (long)mediaTimeUs, (long)systemNano);
                     }
                     break;
+                case EVENT_FIRST_TUNNEL_FRAME_READY:
+                    OnFirstTunnelFrameReadyListener onFirstTunnelFrameReadyListener;
+                    synchronized (mListenerLock) {
+                        onFirstTunnelFrameReadyListener = mOnFirstTunnelFrameReadyListener;
+                    }
+                    if (onFirstTunnelFrameReadyListener == null) {
+                        break;
+                    }
+                    onFirstTunnelFrameReadyListener.onFirstTunnelFrameReady(mCodec);
+                    break;
                 default:
                 {
                     break;
@@ -1923,6 +1936,7 @@
             mEventHandler = null;
         }
         mCallbackHandler = mEventHandler;
+        mOnFirstTunnelFrameReadyHandler = mEventHandler;
         mOnFrameRenderedHandler = mEventHandler;
 
         mBufferLock = new Object();
@@ -2277,6 +2291,9 @@
                 mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
                 mCallbackHandler.removeMessages(EVENT_CALLBACK);
             }
+            if (mOnFirstTunnelFrameReadyHandler != null) {
+                mOnFirstTunnelFrameReadyHandler.removeMessages(EVENT_FIRST_TUNNEL_FRAME_READY);
+            }
             if (mOnFrameRenderedHandler != null) {
                 mOnFrameRenderedHandler.removeMessages(EVENT_FRAME_RENDERED);
             }
@@ -4447,6 +4464,41 @@
             MediaFormat.KEY_LOW_LATENCY;
 
     /**
+     * Control video peek of the first frame when a codec is configured for tunnel mode with
+     * {@link MediaFormat#KEY_AUDIO_SESSION_ID} while the {@link AudioTrack} is paused.
+     *<p>
+     * When disabled (1) after a {@link #flush} or {@link #start}, (2) while the corresponding
+     * {@link AudioTrack} is paused and (3) before any buffers are queued, the first frame is not to
+     * be rendered until either this parameter is enabled or the corresponding {@link AudioTrack}
+     * has begun playback. Once the frame is decoded and ready to be rendered,
+     * {@link OnFirstTunnelFrameReadyListener#onFirstTunnelFrameReady} is called but the frame is
+     * not rendered. The surface continues to show the previously-rendered content, or black if the
+     * surface is new. A subsequent call to {@link AudioTrack#play} renders this frame and triggers
+     * a callback to {@link OnFrameRenderedListener#onFrameRendered}, and video playback begins.
+     *<p>
+     * <b>Note</b>: To clear any previously rendered content and show black, configure the
+     * MediaCodec with {@code KEY_PUSH_BLANK_BUFFERS_ON_STOP(1)}, and call {@link #stop} before
+     * pushing new video frames to the codec.
+     *<p>
+     * When enabled (1) after a {@link #flush} or {@link #start} and (2) while the corresponding
+     * {@link AudioTrack} is paused, the first frame is rendered as soon as it is decoded, or
+     * immediately, if it has already been decoded. If not already decoded, when the frame is
+     * decoded and ready to be rendered,
+     * {@link OnFirstTunnelFrameReadyListener#onFirstTunnelFrameReady} is called. The frame is then
+     * immediately rendered and {@link OnFrameRenderedListener#onFrameRendered} is subsequently
+     * called.
+     *<p>
+     * The value is an Integer object containing the value 1 to enable or the value 0 to disable.
+     *<p>
+     * The default for this parameter is <b>enabled</b>. Once a frame has been rendered, changing
+     * this parameter has no effect until a subsequent {@link #flush} or
+     * {@link #stop}/{@link #start}.
+     *
+     * @see #setParameters(Bundle)
+     */
+    public static final String PARAMETER_KEY_TUNNEL_PEEK = "tunnel-peek";
+
+    /**
      * Communicate additional parameter changes to the component instance.
      * <b>Note:</b> Some of these parameter changes may silently fail to apply.
      *
@@ -4545,6 +4597,55 @@
     }
 
     /**
+     * Listener to be called when the first output frame has been decoded
+     * and is ready to be rendered for a codec configured for tunnel mode with
+     * {@code KEY_AUDIO_SESSION_ID}.
+     *
+     * @see MediaCodec#setOnFirstTunnelFrameReadyListener
+     */
+    public interface OnFirstTunnelFrameReadyListener {
+
+        /**
+         * Called when the first output frame has been decoded and is ready to be
+         * rendered.
+         */
+        void onFirstTunnelFrameReady(@NonNull MediaCodec codec);
+    }
+
+    /**
+     * Registers a callback to be invoked when the first output frame has been decoded
+     * and is ready to be rendered on a codec configured for tunnel mode with {@code
+     * KEY_AUDIO_SESSION_ID}.
+     *
+     * @param handler the callback will be run on the handler's thread. If {@code
+     * null}, the callback will be run on the default thread, which is the looper from
+     * which the codec was created, or a new thread if there was none.
+     *
+     * @param listener the callback that will be run. If {@code null}, clears any registered
+     * listener.
+     */
+    public void setOnFirstTunnelFrameReadyListener(
+            @Nullable Handler handler, @Nullable OnFirstTunnelFrameReadyListener listener) {
+        synchronized (mListenerLock) {
+            mOnFirstTunnelFrameReadyListener = listener;
+            if (listener != null) {
+                EventHandler newHandler = getEventHandlerOn(
+                        handler,
+                        mOnFirstTunnelFrameReadyHandler);
+                if (newHandler != mOnFirstTunnelFrameReadyHandler) {
+                    mOnFirstTunnelFrameReadyHandler.removeMessages(EVENT_FIRST_TUNNEL_FRAME_READY);
+                }
+                mOnFirstTunnelFrameReadyHandler = newHandler;
+            } else if (mOnFirstTunnelFrameReadyHandler != null) {
+                mOnFirstTunnelFrameReadyHandler.removeMessages(EVENT_FIRST_TUNNEL_FRAME_READY);
+            }
+            native_enableOnFirstTunnelFrameReadyListener(listener != null);
+        }
+    }
+
+    private native void native_enableOnFirstTunnelFrameReadyListener(boolean enable);
+
+    /**
      * Listener to be called when an output frame has rendered on the output surface
      *
      * @see MediaCodec#setOnFrameRenderedListener
@@ -4667,6 +4768,8 @@
             EventHandler handler = mEventHandler;
             if (what == EVENT_CALLBACK) {
                 handler = mCallbackHandler;
+            } else if (what == EVENT_FIRST_TUNNEL_FRAME_READY) {
+                handler = mOnFirstTunnelFrameReadyHandler;
             } else if (what == EVENT_FRAME_RENDERED) {
                 handler = mOnFrameRenderedHandler;
             }
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 3de78bb..644afb7 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -99,9 +99,7 @@
 
 
 /**
- * MediaPlayer class can be used to control playback
- * of audio/video files and streams. An example on how to use the methods in
- * this class can be found in {@link android.widget.VideoView}.
+ * MediaPlayer class can be used to control playback of audio/video files and streams.
  *
  * <p>MediaPlayer is not thread-safe. Creation of and all access to player instances
  * should be on the same thread. If registering <a href="#Callbacks">callbacks</a>,
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 02fa040..8daa303 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -299,6 +299,24 @@
     }
 
     /**
+     * Registers a callback to receive route related events when they change.
+     * <p>
+     * If the specified callback is already registered, its registration will be updated for the
+     * given {@link Executor executor}.
+     * <p>
+     * This will be no-op for non-system routers.
+     * @hide
+     */
+    @SystemApi
+    public void registerRouteCallback(@NonNull @CallbackExecutor Executor executor,
+            @NonNull RouteCallback routeCallback) {
+        if (!isSystemRouter()) {
+            return;
+        }
+        registerRouteCallback(executor, routeCallback, RouteDiscoveryPreference.EMPTY);
+    }
+
+    /**
      * Registers a callback to discover routes and to receive events when they change.
      * <p>
      * If the specified callback is already registered, its registration will be updated for the
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 7f5dd5d..ded2e1b 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -81,6 +81,7 @@
     EVENT_CALLBACK = 1,
     EVENT_SET_CALLBACK = 2,
     EVENT_FRAME_RENDERED = 3,
+    EVENT_FIRST_TUNNEL_FRAME_READY = 4,
 };
 
 static struct CryptoErrorCodes {
@@ -269,6 +270,18 @@
     mClass = NULL;
 }
 
+status_t JMediaCodec::enableOnFirstTunnelFrameReadyListener(jboolean enable) {
+    if (enable) {
+        if (mOnFirstTunnelFrameReadyNotification == NULL) {
+            mOnFirstTunnelFrameReadyNotification = new AMessage(kWhatFirstTunnelFrameReady, this);
+        }
+    } else {
+        mOnFirstTunnelFrameReadyNotification.clear();
+    }
+
+    return mCodec->setOnFirstTunnelFrameReadyNotification(mOnFirstTunnelFrameReadyNotification);
+}
+
 status_t JMediaCodec::enableOnFrameRenderedListener(jboolean enable) {
     if (enable) {
         if (mOnFrameRenderedNotification == NULL) {
@@ -1058,6 +1071,27 @@
     env->DeleteLocalRef(obj);
 }
 
+void JMediaCodec::handleFirstTunnelFrameReadyNotification(const sp<AMessage> &msg) {
+    int32_t arg1 = 0, arg2 = 0;
+    jobject obj = NULL;
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+    sp<AMessage> data;
+    CHECK(msg->findMessage("data", &data));
+
+    status_t err = ConvertMessageToMap(env, data, &obj);
+    if (err != OK) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    env->CallVoidMethod(
+            mObject, gFields.postEventFromNativeID,
+            EVENT_FIRST_TUNNEL_FRAME_READY, arg1, arg2, obj);
+
+    env->DeleteLocalRef(obj);
+}
+
 void JMediaCodec::handleFrameRenderedNotification(const sp<AMessage> &msg) {
     int32_t arg1 = 0, arg2 = 0;
     jobject obj = NULL;
@@ -1100,6 +1134,11 @@
             }
             break;
         }
+        case kWhatFirstTunnelFrameReady:
+        {
+            handleFirstTunnelFrameReadyNotification(msg);
+            break;
+        }
         default:
             TRESPASS();
     }
@@ -1256,6 +1295,22 @@
     }
 }
 
+static void android_media_MediaCodec_native_enableOnFirstTunnelFrameReadyListener(
+        JNIEnv *env,
+        jobject thiz,
+        jboolean enabled) {
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL || codec->initCheck() != OK) {
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        return;
+    }
+
+    status_t err = codec->enableOnFirstTunnelFrameReadyListener(enabled);
+
+    throwExceptionAsNecessary(env, err);
+}
+
 static void android_media_MediaCodec_native_enableOnFrameRenderedListener(
         JNIEnv *env,
         jobject thiz,
@@ -3138,6 +3193,9 @@
     { "native_setInputSurface", "(Landroid/view/Surface;)V",
       (void *)android_media_MediaCodec_setInputSurface },
 
+    { "native_enableOnFirstTunnelFrameReadyListener", "(Z)V",
+      (void *)android_media_MediaCodec_native_enableOnFirstTunnelFrameReadyListener },
+
     { "native_enableOnFrameRenderedListener", "(Z)V",
       (void *)android_media_MediaCodec_native_enableOnFrameRenderedListener },
 
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index f16bcf3..5fd6bfd 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -63,6 +63,8 @@
     void release();
     void releaseAsync();
 
+    status_t enableOnFirstTunnelFrameReadyListener(jboolean enable);
+
     status_t enableOnFrameRenderedListener(jboolean enable);
 
     status_t setCallback(jobject cb);
@@ -176,6 +178,7 @@
         kWhatCallbackNotify,
         kWhatFrameRendered,
         kWhatAsyncReleaseComplete,
+        kWhatFirstTunnelFrameReady,
     };
 
     jclass mClass;
@@ -191,6 +194,7 @@
     std::once_flag mAsyncReleaseFlag;
 
     sp<AMessage> mCallbackNotification;
+    sp<AMessage> mOnFirstTunnelFrameReadyNotification;
     sp<AMessage> mOnFrameRenderedNotification;
 
     status_t mInitStatus;
@@ -203,6 +207,7 @@
             jobject *buf) const;
 
     void handleCallback(const sp<AMessage> &msg);
+    void handleFirstTunnelFrameReadyNotification(const sp<AMessage> &msg);
     void handleFrameRenderedNotification(const sp<AMessage> &msg);
 
     DISALLOW_EVIL_CONSTRUCTORS(JMediaCodec);
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index b01878b..85513ca 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -285,6 +285,7 @@
     ATrace_endAsyncSection; # introduced=29
     ATrace_setCounter; # introduced=29
     android_getaddrinfofornetwork; # introduced=23
+    android_getprocnetwork; # introduced=31
     android_setprocnetwork; # introduced=23
     android_setsocknetwork; # introduced=23
     android_res_cancel; # introduced=29
@@ -309,4 +310,4 @@
         ASurfaceControlStats_getAcquireTime*;
         ASurfaceControlStats_getFrameNumber*;
     };
-} LIBANDROID;
\ No newline at end of file
+} LIBANDROID;
diff --git a/native/android/libandroid_net.map.txt b/native/android/libandroid_net.map.txt
index 8d4e900..cc8dd72 100644
--- a/native/android/libandroid_net.map.txt
+++ b/native/android/libandroid_net.map.txt
@@ -14,6 +14,8 @@
     android_res_nquery; # llndk
     android_res_nresult; # llndk
     android_res_nsend; # llndk
+    # These functions have been part of the NDK since API 31.
+    android_getprocnetwork; # llndk
   local:
     *;
 };
diff --git a/native/android/net.c b/native/android/net.c
index a8104fc..d4b8888 100644
--- a/native/android/net.c
+++ b/native/android/net.c
@@ -22,12 +22,13 @@
 #include <stdlib.h>
 #include <sys/limits.h>
 
+// This value MUST be kept in sync with the corresponding value in
+// the android.net.Network#getNetworkHandle() implementation.
+static const uint32_t kHandleMagic = 0xcafed00d;
+static const uint32_t kHandleMagicSize = 32;
 
 static int getnetidfromhandle(net_handle_t handle, unsigned *netid) {
     static const uint32_t k32BitMask = 0xffffffff;
-    // This value MUST be kept in sync with the corresponding value in
-    // the android.net.Network#getNetworkHandle() implementation.
-    static const uint32_t kHandleMagic = 0xcafed00d;
 
     // Check for minimum acceptable version of the API in the low bits.
     if (handle != NETWORK_UNSPECIFIED &&
@@ -41,6 +42,12 @@
     return 1;
 }
 
+static net_handle_t gethandlefromnetid(unsigned netid) {
+    if (netid == NETID_UNSET) {
+        return NETWORK_UNSPECIFIED;
+    }
+    return (((net_handle_t) netid) << kHandleMagicSize) | kHandleMagic;
+}
 
 int android_setsocknetwork(net_handle_t network, int fd) {
     unsigned netid;
@@ -72,6 +79,17 @@
     return rval;
 }
 
+int android_getprocnetwork(net_handle_t *network) {
+    if (network == NULL) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    unsigned netid = getNetworkForProcess();
+    *network = gethandlefromnetid(netid);
+    return 0;
+}
+
 int android_getaddrinfofornetwork(net_handle_t network,
         const char *node, const char *service,
         const struct addrinfo *hints, struct addrinfo **res) {
diff --git a/packages/Connectivity/TEST_MAPPING b/packages/Connectivity/TEST_MAPPING
new file mode 100644
index 0000000..94f9232
--- /dev/null
+++ b/packages/Connectivity/TEST_MAPPING
@@ -0,0 +1,19 @@
+{
+  "imports": [
+    {
+      "path": "frameworks/base/core/java/android/net"
+    },
+    {
+      "path": "packages/modules/NetworkStack"
+    },
+    {
+      "path": "packages/modules/CaptivePortalLogin"
+    },
+    {
+      "path": "packages/modules/Connectivity"
+    },
+    {
+      "path": "packages/modules/Connectivity/Tethering"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp
index 657d5a3..3553c1f 100644
--- a/packages/Connectivity/framework/Android.bp
+++ b/packages/Connectivity/framework/Android.bp
@@ -128,6 +128,7 @@
     srcs: [
         "jni/android_net_NetworkUtils.cpp",
     ],
+    shared_libs: ["libandroid_net"],
     apex_available: [
         "//apex_available:platform",
         "com.android.tethering",
@@ -140,6 +141,7 @@
     srcs: [
         "jni/onload.cpp",
     ],
+    shared_libs: ["libandroid"],
     static_libs: ["libconnectivityframeworkutils"],
     apex_available: [
         "//apex_available:platform",
diff --git a/packages/Connectivity/framework/api/current.txt b/packages/Connectivity/framework/api/current.txt
index e415e01..0a9560a 100644
--- a/packages/Connectivity/framework/api/current.txt
+++ b/packages/Connectivity/framework/api/current.txt
@@ -68,6 +68,7 @@
     method public boolean bindProcessToNetwork(@Nullable android.net.Network);
     method @NonNull public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network getActiveNetwork();
+    method @Nullable @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public android.net.Network getActiveNetworkForUid(int);
     method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo();
     method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo();
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network[] getAllNetworks();
@@ -387,7 +388,9 @@
   public class NetworkRequest implements android.os.Parcelable {
     method public boolean canBeSatisfiedBy(@Nullable android.net.NetworkCapabilities);
     method public int describeContents();
+    method @NonNull public int[] getCapabilities();
     method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier();
+    method @NonNull public int[] getTransportTypes();
     method public boolean hasCapability(int);
     method public boolean hasTransport(int);
     method public void writeToParcel(android.os.Parcel, int);
@@ -396,6 +399,7 @@
 
   public static class NetworkRequest.Builder {
     ctor public NetworkRequest.Builder();
+    ctor public NetworkRequest.Builder(@NonNull android.net.NetworkRequest);
     method public android.net.NetworkRequest.Builder addCapability(int);
     method public android.net.NetworkRequest.Builder addTransportType(int);
     method public android.net.NetworkRequest build();
diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt
index 9376a4b..8b99d21 100644
--- a/packages/Connectivity/framework/api/module-lib-current.txt
+++ b/packages/Connectivity/framework/api/module-lib-current.txt
@@ -13,11 +13,14 @@
     method @NonNull public static String getPrivateDnsMode(@NonNull android.content.Context);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
+    method @Deprecated public boolean requestRouteToHostAddress(int, java.net.InetAddress);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptPartialConnectivity(@NonNull android.net.Network, boolean, boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptUnvalidated(@NonNull android.net.Network, boolean, boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAvoidUnvalidated(@NonNull android.net.Network);
     method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setGlobalProxy(@Nullable android.net.ProxyInfo);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setLegacyLockdownVpnEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull android.os.UserHandle, int, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setRequireVpnForUids(boolean, @NonNull java.util.Collection<android.util.Range<java.lang.Integer>>);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void startCaptivePortalApp(@NonNull android.net.Network);
     method public void systemReady();
@@ -42,6 +45,7 @@
   public final class NetworkCapabilities implements android.os.Parcelable {
     ctor public NetworkCapabilities(@Nullable android.net.NetworkCapabilities, long);
     method @Nullable public java.util.Set<android.util.Range<java.lang.Integer>> getUids();
+    method public boolean hasUnwantedCapability(int);
     field public static final long REDACT_ALL = -1L; // 0xffffffffffffffffL
     field public static final long REDACT_FOR_ACCESS_FINE_LOCATION = 1L; // 0x1L
     field public static final long REDACT_FOR_LOCAL_MAC_ADDRESS = 2L; // 0x2L
@@ -54,7 +58,14 @@
     method @NonNull public android.net.NetworkCapabilities.Builder setUids(@Nullable java.util.Set<android.util.Range<java.lang.Integer>>);
   }
 
+  public class NetworkRequest implements android.os.Parcelable {
+    method @NonNull public int[] getUnwantedCapabilities();
+    method public boolean hasUnwantedCapability(int);
+  }
+
   public static class NetworkRequest.Builder {
+    method @NonNull public android.net.NetworkRequest.Builder addUnwantedCapability(int);
+    method @NonNull public android.net.NetworkRequest.Builder removeUnwantedCapability(int);
     method @NonNull public android.net.NetworkRequest.Builder setUids(@Nullable java.util.Set<android.util.Range<java.lang.Integer>>);
   }
 
diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt
index 358cea8..b19efa3 100644
--- a/packages/Connectivity/framework/api/system-current.txt
+++ b/packages/Connectivity/framework/api/system-current.txt
@@ -52,7 +52,7 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported();
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public int registerNetworkProvider(@NonNull android.net.NetworkProvider);
-    method public void registerQosCallback(@NonNull android.net.QosSocketInfo, @NonNull android.net.QosCallback, @NonNull java.util.concurrent.Executor);
+    method public void registerQosCallback(@NonNull android.net.QosSocketInfo, @NonNull java.util.concurrent.Executor, @NonNull android.net.QosCallback);
     method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
     method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean);
@@ -212,10 +212,12 @@
 
   public abstract class NetworkAgent {
     ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, int, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider);
+    ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkScore, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider);
     method @Nullable public android.net.Network getNetwork();
     method public void markConnected();
     method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData);
     method public void onAutomaticReconnectDisabled();
+    method public void onBandwidthUpdateRequested();
     method public void onNetworkUnwanted();
     method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter);
     method public void onQosCallbackUnregistered(int);
@@ -233,6 +235,7 @@
     method public final void sendQosSessionAvailable(int, int, @NonNull android.telephony.data.EpsBearerQosSessionAttributes);
     method public final void sendQosSessionLost(int, int);
     method public final void sendSocketKeepaliveEvent(int, int);
+    method @Deprecated public void setLegacySubtype(int, @NonNull String);
     method public final void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>);
     method public void unregister();
     field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2
@@ -253,7 +256,12 @@
   public static final class NetworkAgentConfig.Builder {
     ctor public NetworkAgentConfig.Builder();
     method @NonNull public android.net.NetworkAgentConfig build();
+    method @NonNull public android.net.NetworkAgentConfig.Builder disableNat64Detection();
+    method @NonNull public android.net.NetworkAgentConfig.Builder disableProvisioningNotification();
     method @NonNull public android.net.NetworkAgentConfig.Builder setExplicitlySelected(boolean);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyExtraInfo(@NonNull String);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setLegacySubType(int);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setLegacySubTypeName(@NonNull String);
     method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyType(int);
     method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyTypeName(@NonNull String);
     method @NonNull public android.net.NetworkAgentConfig.Builder setPartialConnectivityAcceptable(boolean);
@@ -316,6 +324,19 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int);
   }
 
+  public final class NetworkScore implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getLegacyInt();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkScore> CREATOR;
+  }
+
+  public static final class NetworkScore.Builder {
+    ctor public NetworkScore.Builder();
+    method @NonNull public android.net.NetworkScore build();
+    method @NonNull public android.net.NetworkScore.Builder setLegacyInt(int);
+  }
+
   public final class OemNetworkPreferences implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getNetworkPreferences();
@@ -388,6 +409,7 @@
   }
 
   public abstract class SocketKeepalive implements java.lang.AutoCloseable {
+    field public static final int ERROR_NO_SUCH_SLOT = -33; // 0xffffffdf
     field public static final int SUCCESS = 0; // 0x0
   }
 
diff --git a/packages/Connectivity/framework/jarjar-rules.txt b/packages/Connectivity/framework/jarjar-rules.txt
index 0959840..7474c24 100644
--- a/packages/Connectivity/framework/jarjar-rules.txt
+++ b/packages/Connectivity/framework/jarjar-rules.txt
@@ -1,4 +1,5 @@
 rule com.android.net.module.util.** android.net.connectivity.framework.util.@1
+rule android.net.NetworkFactory* android.net.connectivity.framework.NetworkFactory@1
 
 # TODO (b/149403767): remove the annotations from net-utils-device-common instead of here
 zap android.annotation.**
diff --git a/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp b/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
index c5b1ff8..c7c0bee 100644
--- a/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
+++ b/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
@@ -19,6 +19,7 @@
 #include <vector>
 
 #include <android/file_descriptor_jni.h>
+#include <android/multinetwork.h>
 #include <arpa/inet.h>
 #include <linux/filter.h>
 #include <linux/if_arp.h>
@@ -94,14 +95,21 @@
     }
 }
 
-static jboolean android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId)
+static jboolean android_net_utils_bindProcessToNetworkHandle(JNIEnv *env, jobject thiz,
+        jlong netHandle)
 {
-    return (jboolean) !setNetworkForProcess(netId);
+    return (jboolean) !android_setprocnetwork(netHandle);
 }
 
-static jint android_net_utils_getBoundNetworkForProcess(JNIEnv *env, jobject thiz)
+static jlong android_net_utils_getBoundNetworkHandleForProcess(JNIEnv *env, jobject thiz)
 {
-    return getNetworkForProcess();
+    net_handle_t network;
+    if (android_getprocnetwork(&network) != 0) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+                "android_getprocnetwork(): %s", strerror(errno));
+        return NETWORK_UNSPECIFIED;
+    }
+    return (jlong) network;
 }
 
 static jboolean android_net_utils_bindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz,
@@ -255,8 +263,8 @@
 // clang-format off
 static const JNINativeMethod gNetworkUtilMethods[] = {
     /* name, signature, funcPtr */
-    { "bindProcessToNetwork", "(I)Z", (void*) android_net_utils_bindProcessToNetwork },
-    { "getBoundNetworkForProcess", "()I", (void*) android_net_utils_getBoundNetworkForProcess },
+    { "bindProcessToNetworkHandle", "(J)Z", (void*) android_net_utils_bindProcessToNetworkHandle },
+    { "getBoundNetworkHandleForProcess", "()J", (void*) android_net_utils_getBoundNetworkHandleForProcess },
     { "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
     { "bindSocketToNetwork", "(Ljava/io/FileDescriptor;I)I", (void*) android_net_utils_bindSocketToNetwork },
     { "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index f8a0e4e..d490618 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -1083,8 +1083,7 @@
      *
      * @return a {@link Network} object for the current default network for the
      *         given UID or {@code null} if no default network is currently active
-     *
-     * @hide
+     * TODO: b/183465229 Cleanup getActiveNetworkForUid once b/165835257 is fixed
      */
     @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
     @Nullable
@@ -1125,12 +1124,13 @@
      * @param ranges the UID ranges to restrict
      * @param requireVpn whether the specified UID ranges must use a VPN
      *
-     * TODO: expose as @SystemApi.
      * @hide
      */
     @RequiresPermission(anyOf = {
             NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
-            android.Manifest.permission.NETWORK_STACK})
+            android.Manifest.permission.NETWORK_STACK,
+            android.Manifest.permission.NETWORK_SETTINGS})
+    @SystemApi(client = MODULE_LIBRARIES)
     public void setRequireVpnForUids(boolean requireVpn,
             @NonNull Collection<Range<Integer>> ranges) {
         Objects.requireNonNull(ranges);
@@ -1174,13 +1174,13 @@
      *
      * @param enabled whether legacy lockdown VPN is enabled or disabled
      *
-     * TODO: @SystemApi(client = MODULE_LIBRARIES)
-     *
      * @hide
      */
     @RequiresPermission(anyOf = {
             NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_STACK,
             android.Manifest.permission.NETWORK_SETTINGS})
+    @SystemApi(client = MODULE_LIBRARIES)
     public void setLegacyLockdownVpnEnabled(boolean enabled) {
         try {
             mService.setLegacyLockdownVpnEnabled(enabled);
@@ -2127,6 +2127,7 @@
      */
     @Deprecated
     @UnsupportedAppUsage
+    @SystemApi(client = MODULE_LIBRARIES)
     public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) {
         checkLegacyRoutingApiAccess();
         try {
@@ -4942,20 +4943,20 @@
      * {@link QosCallback#onError(QosCallbackException)}.  see: {@link QosCallbackException}.
      *
      * @param socketInfo the socket information used to match QoS events
-     * @param callback receives qos events that satisfy socketInfo
      * @param executor The executor on which the callback will be invoked. The provided
      *                 {@link Executor} must run callback sequentially, otherwise the order of
-     *                 callbacks cannot be guaranteed.
+     *                 callbacks cannot be guaranteed.onQosCallbackRegistered
+     * @param callback receives qos events that satisfy socketInfo
      *
      * @hide
      */
     @SystemApi
     public void registerQosCallback(@NonNull final QosSocketInfo socketInfo,
-            @NonNull final QosCallback callback,
-            @CallbackExecutor @NonNull final Executor executor) {
+            @CallbackExecutor @NonNull final Executor executor,
+            @NonNull final QosCallback callback) {
         Objects.requireNonNull(socketInfo, "socketInfo must be non-null");
-        Objects.requireNonNull(callback, "callback must be non-null");
         Objects.requireNonNull(executor, "executor must be non-null");
+        Objects.requireNonNull(callback, "callback must be non-null");
 
         try {
             synchronized (mQosCallbackConnections) {
diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgent.java b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
index 3863ed1..b3d9616 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkAgent.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
@@ -362,9 +362,8 @@
     public static final int CMD_UNREGISTER_QOS_CALLBACK = BASE + 21;
 
     private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) {
-        // The subtype can be changed with (TODO) setLegacySubtype, but it starts
-        // with 0 (TelephonyManager.NETWORK_TYPE_UNKNOWN) and an empty description.
-        final NetworkInfo ni = new NetworkInfo(config.legacyType, 0, config.legacyTypeName, "");
+        final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacySubType,
+                config.legacyTypeName, config.legacySubTypeName);
         ni.setIsAvailable(true);
         ni.setDetailedState(NetworkInfo.DetailedState.CONNECTING, null /* reason */,
                 config.getLegacyExtraInfo());
@@ -390,7 +389,6 @@
      * @param score the initial score of this network. Update with sendNetworkScore.
      * @param config an immutable {@link NetworkAgentConfig} for this agent.
      * @param provider the {@link NetworkProvider} managing this agent.
-     * @hide TODO : unhide when impl is complete
      */
     public NetworkAgent(@NonNull Context context, @NonNull Looper looper, @NonNull String logTag,
             @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp,
@@ -829,6 +827,7 @@
      * @hide
      */
     @Deprecated
+    @SystemApi
     public void setLegacySubtype(final int legacySubtype, @NonNull final String legacySubtypeName) {
         mNetworkInfo.setSubtype(legacySubtype, legacySubtypeName);
         queueOrSendNetworkInfo(mNetworkInfo);
@@ -962,6 +961,7 @@
      * shall try to overwrite this method and produce a bandwidth update if capable.
      * @hide
      */
+    @SystemApi
     public void onBandwidthUpdateRequested() {
         pollLceData();
     }
diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java b/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java
index 5e50a64..fb6fcc1 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java
@@ -165,6 +165,12 @@
     }
 
     /**
+     * The legacy Sub type of this network agent, or TYPE_NONE if unset.
+     * @hide
+     */
+    public int legacySubType = ConnectivityManager.TYPE_NONE;
+
+    /**
      * Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network.
      * Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode.
      *
@@ -190,6 +196,13 @@
     }
 
     /**
+     * The name of the legacy Sub network type. It's a free-form string.
+     * @hide
+     */
+    @NonNull
+    public String legacySubTypeName = "";
+
+    /**
      * The legacy extra info of the agent. The extra info should only be :
      * <ul>
      *   <li>For cellular agents, the APN name.</li>
@@ -225,6 +238,8 @@
             skip464xlat = nac.skip464xlat;
             legacyType = nac.legacyType;
             legacyTypeName = nac.legacyTypeName;
+            legacySubType = nac.legacySubType;
+            legacySubTypeName = nac.legacySubTypeName;
             mLegacyExtraInfo = nac.mLegacyExtraInfo;
         }
     }
@@ -290,7 +305,6 @@
          * and reduce idle traffic on networks that are known to be IPv6-only without a NAT64.
          *
          * @return this builder, to facilitate chaining.
-         * @hide
          */
         @NonNull
         public Builder disableNat64Detection() {
@@ -303,7 +317,6 @@
          * perform its own carrier-specific provisioning procedure.
          *
          * @return this builder, to facilitate chaining.
-         * @hide
          */
         @NonNull
         public Builder disableProvisioningNotification() {
@@ -324,6 +337,18 @@
         }
 
         /**
+         * Sets the legacy sub-type for this network.
+         *
+         * @param legacySubType the type
+         * @return this builder, to facilitate chaining.
+         */
+        @NonNull
+        public Builder setLegacySubType(final int legacySubType) {
+            mConfig.legacySubType = legacySubType;
+            return this;
+        }
+
+        /**
          * Sets the name of the legacy type of the agent. It's a free-form string used in logging.
          * @param legacyTypeName the name
          * @return this builder, to facilitate chaining.
@@ -335,10 +360,20 @@
         }
 
         /**
+         * Sets the name of the legacy Sub-type of the agent. It's a free-form string.
+         * @param legacySubTypeName the name
+         * @return this builder, to facilitate chaining.
+         */
+        @NonNull
+        public Builder setLegacySubTypeName(@NonNull String legacySubTypeName) {
+            mConfig.legacySubTypeName = legacySubTypeName;
+            return this;
+        }
+
+        /**
          * Sets the legacy extra info of the agent.
          * @param legacyExtraInfo the legacy extra info.
          * @return this builder, to facilitate chaining.
-         * @hide
          */
         @NonNull
         public Builder setLegacyExtraInfo(@NonNull String legacyExtraInfo) {
@@ -412,6 +447,8 @@
         out.writeInt(skip464xlat ? 1 : 0);
         out.writeInt(legacyType);
         out.writeString(legacyTypeName);
+        out.writeInt(legacySubType);
+        out.writeString(legacySubTypeName);
         out.writeString(mLegacyExtraInfo);
     }
 
@@ -429,6 +466,8 @@
             networkAgentConfig.skip464xlat = in.readInt() != 0;
             networkAgentConfig.legacyType = in.readInt();
             networkAgentConfig.legacyTypeName = in.readString();
+            networkAgentConfig.legacySubType = in.readInt();
+            networkAgentConfig.legacySubTypeName = in.readString();
             networkAgentConfig.mLegacyExtraInfo = in.readString();
             return networkAgentConfig;
         }
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index 6572bbde..4924851 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -602,19 +602,31 @@
     }
 
     /**
-     * Removes (if found) the given capability from this {@code NetworkCapability} instance.
+     * Removes (if found) the given capability from this {@code NetworkCapability}
+     * instance that were added via addCapability(int) or setCapabilities(int[], int[]).
      *
      * @param capability the capability to be removed.
      * @return This NetworkCapabilities instance, to facilitate chaining.
      * @hide
      */
     public @NonNull NetworkCapabilities removeCapability(@NetCapability int capability) {
-        // Note that this method removes capabilities that were added via addCapability(int),
-        // addUnwantedCapability(int) or setCapabilities(int[], int[]).
         checkValidCapability(capability);
         final long mask = ~(1 << capability);
         mNetworkCapabilities &= mask;
-        mUnwantedNetworkCapabilities &= mask;
+        return this;
+    }
+
+    /**
+     * Removes (if found) the given unwanted capability from this {@code NetworkCapability}
+     * instance that were added via addUnwantedCapability(int) or setCapabilities(int[], int[]).
+     *
+     * @param capability the capability to be removed.
+     * @return This NetworkCapabilities instance, to facilitate chaining.
+     * @hide
+     */
+    public @NonNull NetworkCapabilities removeUnwantedCapability(@NetCapability int capability) {
+        checkValidCapability(capability);
+        mUnwantedNetworkCapabilities &= ~(1 << capability);
         return this;
     }
 
@@ -686,6 +698,7 @@
     }
 
     /** @hide */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public boolean hasUnwantedCapability(@NetCapability int capability) {
         return isValidCapability(capability)
                 && ((mUnwantedNetworkCapabilities & (1 << capability)) != 0);
@@ -699,10 +712,16 @@
         return ((mNetworkCapabilities & CONNECTIVITY_MANAGED_CAPABILITIES) != 0);
     }
 
-    /** Note this method may result in having the same capability in wanted and unwanted lists. */
     private void combineNetCapabilities(@NonNull NetworkCapabilities nc) {
-        this.mNetworkCapabilities |= nc.mNetworkCapabilities;
-        this.mUnwantedNetworkCapabilities |= nc.mUnwantedNetworkCapabilities;
+        final long wantedCaps = this.mNetworkCapabilities | nc.mNetworkCapabilities;
+        final long unwantedCaps =
+                this.mUnwantedNetworkCapabilities | nc.mUnwantedNetworkCapabilities;
+        if ((wantedCaps & unwantedCaps) != 0) {
+            throw new IllegalArgumentException(
+                    "Cannot have the same capability in wanted and unwanted lists.");
+        }
+        this.mNetworkCapabilities = wantedCaps;
+        this.mUnwantedNetworkCapabilities = unwantedCaps;
     }
 
     /**
diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
index cf131f0..38691ef 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
@@ -216,6 +216,14 @@
         }
 
         /**
+         * Creates a new Builder of NetworkRequest from an existing instance.
+         */
+        public Builder(@NonNull final NetworkRequest request) {
+            Objects.requireNonNull(request);
+            mNetworkCapabilities = request.networkCapabilities;
+        }
+
+        /**
          * Build {@link NetworkRequest} give the current set of capabilities.
          */
         public NetworkRequest build() {
@@ -305,12 +313,31 @@
          *
          * @hide
          */
+        @NonNull
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
         public Builder addUnwantedCapability(@NetworkCapabilities.NetCapability int capability) {
             mNetworkCapabilities.addUnwantedCapability(capability);
             return this;
         }
 
         /**
+         * Removes (if found) the given unwanted capability from this builder instance.
+         *
+         * @param capability The unwanted capability to remove.
+         * @return The builder to facilitate chaining.
+         *
+         * @hide
+         */
+        @NonNull
+        @SuppressLint("BuilderSetStyle")
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        public Builder removeUnwantedCapability(@NetworkCapabilities.NetCapability int capability) {
+            mNetworkCapabilities.removeUnwantedCapability(capability);
+            return this;
+        }
+
+        /**
          * Completely clears all the {@code NetworkCapabilities} from this builder instance,
          * removing even the capabilities that are set by default when the object is constructed.
          *
@@ -567,6 +594,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public boolean hasUnwantedCapability(@NetCapability int capability) {
         return networkCapabilities.hasUnwantedCapability(capability);
     }
@@ -671,4 +699,43 @@
     public int hashCode() {
         return Objects.hash(requestId, legacyType, networkCapabilities, type);
     }
+
+    /**
+     * Gets all the capabilities set on this {@code NetworkRequest} instance.
+     *
+     * @return an array of capability values for this instance.
+     */
+    @NonNull
+    public @NetCapability int[] getCapabilities() {
+        // No need to make a defensive copy here as NC#getCapabilities() already returns
+        // a new array.
+        return networkCapabilities.getCapabilities();
+    }
+
+    /**
+     * Gets all the unwanted capabilities set on this {@code NetworkRequest} instance.
+     *
+     * @return an array of unwanted capability values for this instance.
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public @NetCapability int[] getUnwantedCapabilities() {
+        // No need to make a defensive copy here as NC#getUnwantedCapabilities() already returns
+        // a new array.
+        return networkCapabilities.getUnwantedCapabilities();
+    }
+
+    /**
+     * Gets all the transports set on this {@code NetworkRequest} instance.
+     *
+     * @return an array of transport type values for this instance.
+     */
+    @NonNull
+    public @Transport int[] getTransportTypes() {
+        // No need to make a defensive copy here as NC#getTransportTypes() already returns
+        // a new array.
+        return networkCapabilities.getTransportTypes();
+    }
 }
diff --git a/packages/Connectivity/framework/src/android/net/NetworkScore.java b/packages/Connectivity/framework/src/android/net/NetworkScore.java
index eadcb2d..6584993 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkScore.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkScore.java
@@ -17,6 +17,7 @@
 package android.net;
 
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -29,7 +30,7 @@
  * network is considered for a particular use.
  * @hide
  */
-// TODO : @SystemApi when the implementation is complete
+@SystemApi
 public final class NetworkScore implements Parcelable {
     // This will be removed soon. Do *NOT* depend on it for any new code that is not part of
     // a migration.
@@ -62,6 +63,8 @@
 
     /**
      * @return whether this score has a particular policy.
+     *
+     * @hide
      */
     @VisibleForTesting
     public boolean hasPolicy(final int policy) {
diff --git a/packages/Connectivity/framework/src/android/net/NetworkUtils.java b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
index c0f2628..16ae55f 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkUtils.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import static android.net.ConnectivityManager.NETID_UNSET;
+
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.system.ErrnoException;
@@ -55,6 +57,8 @@
      */
     public static native void detachBPFFilter(FileDescriptor fd) throws SocketException;
 
+    private static native boolean bindProcessToNetworkHandle(long netHandle);
+
     /**
      * Binds the current process to the network designated by {@code netId}.  All sockets created
      * in the future (and not explicitly bound via a bound {@link SocketFactory} (see
@@ -63,13 +67,20 @@
      * is by design so an application doesn't accidentally use sockets it thinks are still bound to
      * a particular {@code Network}.  Passing NETID_UNSET clears the binding.
      */
-    public native static boolean bindProcessToNetwork(int netId);
+    public static boolean bindProcessToNetwork(int netId) {
+        return bindProcessToNetworkHandle(new Network(netId).getNetworkHandle());
+    }
+
+    private static native long getBoundNetworkHandleForProcess();
 
     /**
      * Return the netId last passed to {@link #bindProcessToNetwork}, or NETID_UNSET if
      * {@link #unbindProcessToNetwork} has been called since {@link #bindProcessToNetwork}.
      */
-    public native static int getBoundNetworkForProcess();
+    public static int getBoundNetworkForProcess() {
+        final long netHandle = getBoundNetworkHandleForProcess();
+        return netHandle == 0L ? NETID_UNSET : Network.fromNetworkHandle(netHandle).getNetId();
+    }
 
     /**
      * Binds host resolutions performed by this process to the network designated by {@code netId}.
@@ -323,22 +334,7 @@
      */
     @UnsupportedAppUsage
     public static String trimV4AddrZeros(String addr) {
-        if (addr == null) return null;
-        String[] octets = addr.split("\\.");
-        if (octets.length != 4) return addr;
-        StringBuilder builder = new StringBuilder(16);
-        String result = null;
-        for (int i = 0; i < 4; i++) {
-            try {
-                if (octets[i].length() > 3) return addr;
-                builder.append(Integer.parseInt(octets[i]));
-            } catch (NumberFormatException e) {
-                return addr;
-            }
-            if (i < 3) builder.append('.');
-        }
-        result = builder.toString();
-        return result;
+        return Inet4AddressUtils.trimAddressZeros(addr);
     }
 
     /**
diff --git a/packages/Connectivity/framework/src/android/net/SocketKeepalive.java b/packages/Connectivity/framework/src/android/net/SocketKeepalive.java
index d007a95..f6cae72 100644
--- a/packages/Connectivity/framework/src/android/net/SocketKeepalive.java
+++ b/packages/Connectivity/framework/src/android/net/SocketKeepalive.java
@@ -55,36 +55,68 @@
     static final String TAG = "SocketKeepalive";
 
     /**
-     * No errors.
+     * Success. It indicates there is no error.
      * @hide
      */
     @SystemApi
     public static final int SUCCESS = 0;
 
-    /** @hide */
+    /**
+     * No keepalive. This should only be internally as it indicates There is no keepalive.
+     * It should not propagate to applications.
+     * @hide
+     */
     public static final int NO_KEEPALIVE = -1;
 
-    /** @hide */
+    /**
+     * Data received.
+     * @hide
+     */
     public static final int DATA_RECEIVED = -2;
 
-    /** @hide */
+    /**
+     * The binder died.
+     * @hide
+     */
     public static final int BINDER_DIED = -10;
 
-    /** The specified {@code Network} is not connected. */
+    /**
+     * The invalid network. It indicates the specified {@code Network} is not connected.
+     */
     public static final int ERROR_INVALID_NETWORK = -20;
-    /** The specified IP addresses are invalid. For example, the specified source IP address is
-     * not configured on the specified {@code Network}. */
+
+    /**
+     * The invalid IP addresses. Indicates the specified IP addresses are invalid.
+     * For example, the specified source IP address is not configured on the
+     * specified {@code Network}.
+     */
     public static final int ERROR_INVALID_IP_ADDRESS = -21;
-    /** The requested port is invalid. */
+
+    /**
+     * The port is invalid.
+     */
     public static final int ERROR_INVALID_PORT = -22;
-    /** The packet length is invalid (e.g., too long). */
+
+    /**
+     * The length is invalid (e.g. too long).
+     */
     public static final int ERROR_INVALID_LENGTH = -23;
-    /** The packet transmission interval is invalid (e.g., too short). */
+
+    /**
+     * The interval is invalid (e.g. too short).
+     */
     public static final int ERROR_INVALID_INTERVAL = -24;
-    /** The target socket is invalid. */
+
+    /**
+     * The socket is invalid.
+     */
     public static final int ERROR_INVALID_SOCKET = -25;
-    /** The target socket is not idle. */
+
+    /**
+     * The socket is not idle.
+     */
     public static final int ERROR_SOCKET_NOT_IDLE = -26;
+
     /**
      * The stop reason is uninitialized. This should only be internally used as initial state
      * of stop reason, instead of propagating to application.
@@ -92,15 +124,29 @@
      */
     public static final int ERROR_STOP_REASON_UNINITIALIZED = -27;
 
-    /** The device does not support this request. */
+    /**
+     * The request is unsupported.
+     */
     public static final int ERROR_UNSUPPORTED = -30;
-    /** @hide TODO: delete when telephony code has been updated. */
-    public static final int ERROR_HARDWARE_UNSUPPORTED = ERROR_UNSUPPORTED;
-    /** The hardware returned an error. */
+
+    /**
+     * There was a hardware error.
+     */
     public static final int ERROR_HARDWARE_ERROR = -31;
-    /** The limitation of resource is reached. */
+
+    /**
+     * Resources are insufficient (e.g. all hardware slots are in use).
+     */
     public static final int ERROR_INSUFFICIENT_RESOURCES = -32;
 
+    /**
+     * There was no such slot. This should only be internally as it indicates
+     * a programming error in the system server. It should not propagate to
+     * applications.
+     * @hide
+     */
+    @SystemApi
+    public static final int ERROR_NO_SUCH_SLOT = -33;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -111,7 +157,8 @@
             ERROR_INVALID_LENGTH,
             ERROR_INVALID_INTERVAL,
             ERROR_INVALID_SOCKET,
-            ERROR_SOCKET_NOT_IDLE
+            ERROR_SOCKET_NOT_IDLE,
+            ERROR_NO_SUCH_SLOT
     })
     public @interface ErrorCode {}
 
@@ -122,7 +169,6 @@
             ERROR_INVALID_LENGTH,
             ERROR_UNSUPPORTED,
             ERROR_INSUFFICIENT_RESOURCES,
-            ERROR_HARDWARE_UNSUPPORTED
     })
     public @interface KeepaliveEvent {}
 
diff --git a/packages/Connectivity/service/Android.bp b/packages/Connectivity/service/Android.bp
index f630cea..37dd9ff 100644
--- a/packages/Connectivity/service/Android.bp
+++ b/packages/Connectivity/service/Android.bp
@@ -51,23 +51,35 @@
 
 java_library {
     name: "service-connectivity-pre-jarjar",
+    sdk_version: "system_server_current",
     srcs: [
-        ":framework-connectivity-shared-srcs",
         ":connectivity-service-srcs",
+        ":framework-connectivity-shared-srcs",
+        ":services-connectivity-shared-srcs",
+        // TODO: move to net-utils-device-common, enable shrink optimization to avoid extra classes
+        ":net-module-utils-srcs",
     ],
     libs: [
-        "android.net.ipsec.ike",
-        "services.core",
-        "services.net",
+        // TODO (b/183097033) remove once system_server_current includes core_current
+        "stable.core.platform.api.stubs",
+        "android_system_server_stubs_current",
+        "framework-annotations-lib",
+        "framework-connectivity.impl",
+        "framework-tethering.stubs.module_lib",
+        "framework-wifi.stubs.module_lib",
         "unsupportedappusage",
         "ServiceConnectivityResources",
     ],
     static_libs: [
+        "dnsresolver_aidl_interface-V7-java",
         "modules-utils-os",
         "net-utils-device-common",
         "net-utils-framework-common",
         "netd-client",
+        "netlink-client",
+        "networkstack-client",
         "PlatformProperties",
+        "service-connectivity-protos",
     ],
     apex_available: [
         "//apex_available:platform",
@@ -76,7 +88,24 @@
 }
 
 java_library {
+    name: "service-connectivity-protos",
+    sdk_version: "system_current",
+    proto: {
+        type: "nano",
+    },
+    srcs: [
+        ":system-messages-proto-src",
+    ],
+    libs: ["libprotobuf-java-nano"],
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.tethering",
+    ],
+}
+
+java_library {
     name: "service-connectivity",
+    sdk_version: "system_server_current",
     installable: true,
     static_libs: [
         "service-connectivity-pre-jarjar",
diff --git a/packages/Connectivity/service/jarjar-rules.txt b/packages/Connectivity/service/jarjar-rules.txt
index a7b419b..5caa11b 100644
--- a/packages/Connectivity/service/jarjar-rules.txt
+++ b/packages/Connectivity/service/jarjar-rules.txt
@@ -12,3 +12,6 @@
 # the one in com.android.internal.util
 rule android.util.IndentingPrintWriter* android.connectivity.util.IndentingPrintWriter@1
 rule com.android.internal.util.** com.android.connectivity.util.@1
+
+rule com.android.internal.messages.** com.android.connectivity.messages.@1
+rule com.google.protobuf.** com.android.connectivity.protobuf.@1
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java b/packages/Connectivity/service/proto/connectivityproto.proto
similarity index 61%
copy from services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
copy to packages/Connectivity/service/proto/connectivityproto.proto
index cab5ad2..a992d7c 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
+++ b/packages/Connectivity/service/proto/connectivityproto.proto
@@ -14,17 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.timezonedetector.location;
+syntax = "proto2";
 
-import android.annotation.NonNull;
-
-import com.android.i18n.timezone.ZoneInfoDb;
-
-class ZoneInfoDbTimeZoneIdValidator implements
-        LocationTimeZoneProvider.TimeZoneIdValidator {
-
-    @Override
-    public boolean isValid(@NonNull String timeZoneId) {
-        return ZoneInfoDb.getInstance().hasTimeZone(timeZoneId);
-    }
-}
+// Connectivity protos can be created in this directory. Note this file must be included before
+// building system-messages-proto, otherwise it will not build by itself.
diff --git a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
index 7cc5994..c4c60ea 100644
--- a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
+++ b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
@@ -151,20 +151,14 @@
         }
 
         @Override
-        public void onInitialize(boolean allowed, ProviderProperties properties, String packageName,
-                String attributionTag) {
-
-        }
+        public void onInitialize(boolean allowed, ProviderProperties properties,
+                String attributionTag) {}
 
         @Override
-        public void onSetAllowed(boolean allowed) {
-
-        }
+        public void onSetAllowed(boolean allowed) {}
 
         @Override
-        public void onSetProperties(ProviderProperties properties) {
-
-        }
+        public void onSetProperties(ProviderProperties properties) {}
 
         @Override
         public void onReportLocation(Location location) {
@@ -177,9 +171,7 @@
         }
 
         @Override
-        public void onFlushComplete() {
-
-        }
+        public void onFlushComplete() {}
 
         public Location getNextLocation(long timeoutMs) throws InterruptedException {
             return mLocations.poll(timeoutMs, TimeUnit.MILLISECONDS);
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index d3e4d1c..1c112c6 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -211,7 +211,7 @@
     <string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Wi-Fi ሲገናኝ የማረም ሁነታ"</string>
     <string name="adb_wireless_error" msgid="721958772149779856">"ስህተት"</string>
     <string name="adb_wireless_settings" msgid="2295017847215680229">"ገመድ-አልባ debugging"</string>
-    <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"የሚገኙ መሣሪያዎችን ለመመልከትና ለመጠቀም ገመድ-አልባ debuggingን ያብሩ"</string>
+    <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"የሚገኙ መሣሪያዎችን ለመመልከትና ለመጠቀም ገመድ-አልባ ማረምን ያብሩ"</string>
     <string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"የQR ኮድን በመጠቀም መሣሪያን ያጣምሩ"</string>
     <string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"የQR ኮድ መቃኛን በመጠቀም አዲስ መሣሪያዎችን ያጣምሩ"</string>
     <string name="adb_pair_method_code_title" msgid="1122590300445142904">"የማጣመሪያ ኮድን በመጠቀም መሣሪያን ያጣምሩ"</string>
@@ -303,7 +303,7 @@
     <string name="adb_warning_title" msgid="7708653449506485728">"የUSB ማረሚያ ይፈቀድ?"</string>
     <string name="adb_warning_message" msgid="8145270656419669221">"የUSB አድስ ለግንባታ አላማ ብቻ የታሰበ ነው። ከኮምፒዩተርህ ወደ መሳሪያህ ውሂብ ለመገልበጥ፣ መሣሪያህ ላይ ያለ ማሳወቂያ መተግበሪያዎችን መጫን፣ እና ማስታወሻ ውሂብ ማንበብ ለመጠቀም ይቻላል።"</string>
     <string name="adbwifi_warning_title" msgid="727104571653031865">"ገመድ-አልባ debugging ይፈቀድ?"</string>
-    <string name="adbwifi_warning_message" msgid="8005936574322702388">"ገመድ-አልባ debugging ለግንባታ አላማዎች ብቻ የታሰበ ነው። ውሂብን ከኮምፒዩተርዎ ወደ መሳሪያዎ ለመቅዳት፣ መሣሪያዎ ላይ ያለማሳወቂያ መተግበሪያዎችን ለመጫን እና የምዝግብ ማስታወሻ ውሂብን ለማንበብ ይጠቀሙበት።"</string>
+    <string name="adbwifi_warning_message" msgid="8005936574322702388">"ገመድ-አልባ ማረም ለግንባታ አላማዎች ብቻ የታሰበ ነው። ውሂብን ከኮምፒዩተርዎ ወደ መሳሪያዎ ለመቅዳት፣ መሣሪያዎ ላይ ያለማሳወቂያ መተግበሪያዎችን ለመጫን እና የምዝግብ ማስታወሻ ውሂብን ለማንበብ ይጠቀሙበት።"</string>
     <string name="adb_keys_warning_message" msgid="2968555274488101220">"የዩ ኤስ ቢ ማረም መዳረሻ ከዚህ ቀደም ፍቃድ ከሰጧቸው ኮምፒውተሮች ላይ ይሻሩ?"</string>
     <string name="dev_settings_warning_title" msgid="8251234890169074553">"የግንባታ ቅንብሮችን ፍቀድ?"</string>
     <string name="dev_settings_warning_message" msgid="37741686486073668">"እነዚህ ቅንብሮች  የታሰቡት ለግንባታ አጠቃቀም ብቻ ናቸው። መሳሪያህን እና በሱ ላይ ያሉትን መተግበሪያዎች እንዲበለሹ ወይም በትክክል እንዳይሰሩ ሊያደርጉ ይችላሉ።"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index a0bf4b1..765f31c 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -424,8 +424,7 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (Rot-Grün-Sehschwäche)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (Blau-Gelb-Sehschwäche)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Farbkorrektur"</string>
-    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (2333641630205214702) -->
-    <skip />
+    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"Hier kannst du anpassen, wie Farben auf deinem Gerät dargestellt werden sollen. Das kann in folgenden Fällen hilfreich sein:&lt;br/&gt;&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Wenn Farben genauer dargestellt werden sollen&lt;/li&gt; &lt;li&gt;&amp;nbsp;Wenn du Farben entfernen möchtest, um dich besser konzentrieren zu können&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Außer Kraft gesetzt von \"<xliff:g id="TITLE">%1$s</xliff:g>\""</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Noch etwa <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index a18c0f1..b5da6be 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -424,8 +424,7 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanopia (gorri-berdeak)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanopia (urdin-horia)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Koloreen zuzenketa"</string>
-    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (2333641630205214702) -->
-    <skip />
+    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"Doitu nola bistaratzen diren koloreak gailuan. Kasu hauetan izan daiteke lagungarria:&lt;br/&gt;&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Koloreak zehatzago ikusi nahi dituzunean.&lt;/li&gt; &lt;li&gt;&amp;nbsp;Hobeto fokuratzeko, koloreak kendu nahi dituzunean.&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> hobespena gainjarri zaio"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> inguru gelditzen dira"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index fc788b6..a351ca0 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -424,8 +424,7 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (rouge/vert)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (bleu-jaune)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correction des couleurs"</string>
-    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (2333641630205214702) -->
-    <skip />
+    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"Ajustez l\'affichage des couleurs sur votre appareil. Cette option peut vous être utile pour :&lt;br/&gt;&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Accentuer la précision des couleurs&lt;/li&gt; &lt;li&gt;&amp;nbsp;Supprimer les couleurs pour mieux vous concentrer&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Remplacé par <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Temps restant : environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 90df3b1..23baeb9 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -131,7 +131,7 @@
     <string name="bluetooth_hearingaid_left_pairing_message" msgid="8561855779703533591">"מתבצעת התאמה של מכשיר שמיעה שמאלי…"</string>
     <string name="bluetooth_hearingaid_right_pairing_message" msgid="2655347721696331048">"מתבצעת התאמה של מכשיר שמיעה ימני…"</string>
     <string name="bluetooth_hearingaid_left_battery_level" msgid="7375621694748104876">"שמאלי - טעינת הסוללה: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
-    <string name="bluetooth_hearingaid_right_battery_level" msgid="1850094448499089312">"ימני - טעינת הסוללה: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
+    <string name="bluetooth_hearingaid_right_battery_level" msgid="1850094448499089312">"ימני – טעינת הסוללה: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
     <string name="accessibility_wifi_off" msgid="1195445715254137155">"‏Wi-Fi כבוי."</string>
     <string name="accessibility_no_wifi" msgid="5297119459491085771">"‏Wi-Fi מנותק."</string>
     <string name="accessibility_wifi_one_bar" msgid="6025652717281815212">"‏פס אחד של Wi-Fi."</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index e2b62a4..6c6a5d9 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -424,8 +424,7 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ಪ್ರೊಟನೋಮಲಿ (ಕೆಂಪು-ಹಸಿರು)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ಟ್ರಿಟನೋಮಲಿ (ನೀಲಿ-ಹಳದಿ)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ಬಣ್ಣದ ತಿದ್ದುಪಡಿ"</string>
-    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (2333641630205214702) -->
-    <skip />
+    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಬಣ್ಣಗಳು ಹೇಗೆ ಡಿಸ್‌ಪ್ಲೇ ಆಗುತ್ತವೆ ಎಂಬುದನ್ನು ಹೊಂದಿಸಿ. ನೀವು ಬಣ್ಣಗಳನ್ನು ಹೆಚ್ಚು ನಿಖರವಾಗಿ ನೋಡಲು ಬಯಸಿದಾಗ:&lt;br/&gt;&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;ಇದು ಸಹಾಯಕವಾಗಿರುತ್ತದೆ&lt;/li&gt; &lt;li&gt;&amp;nbsp;ನಿಮಗೆ ಗಮನಹರಿಸಲು ಸಹಾಯ ಮಾಡಲು ಬಣ್ಣಗಳನ್ನು ತೆಗೆದುಹಾಕಿ&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ಮೂಲಕ ಅತಿಕ್ರಮಿಸುತ್ತದೆ"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ಸಮಯ ಬಾಕಿ ಉಳಿದಿದೆ"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 2b351bc..7ec137a 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -424,8 +424,7 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"प्रोटानेमली (रातो, हरियो)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ट्रिटानोमेली (निलो-पंहेलो)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"रङ्ग सुधार"</string>
-    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (2333641630205214702) -->
-    <skip />
+    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"तपाईंको यन्त्रमा रङहरू कस्ता देखिन्छन् भन्ने कुरा मिलाउनुहोस्। यो सुविधा निम्न अवस्थामा उपयोगी हुन सक्छ:&lt;br/&gt;&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;तपाईं अझ सटीक रूपमा रङहरू देख्न चाहनुहुन्छ भने&lt;/li&gt; &lt;li&gt;&amp;nbsp;तपाईं कुनै कुरामा ध्यान केन्द्रित गर्न रङहरू हटाउन चाहनुहुन्छ भने&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> द्वारा अधिरोहित"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"लगभग <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाँकी छ"</string>
diff --git a/packages/SettingsLib/res/values-nl/arrays.xml b/packages/SettingsLib/res/values-nl/arrays.xml
index b5d064e..86b34f54 100644
--- a/packages/SettingsLib/res/values-nl/arrays.xml
+++ b/packages/SettingsLib/res/values-nl/arrays.xml
@@ -248,8 +248,8 @@
   </string-array>
   <string-array name="debug_hw_overdraw_entries">
     <item msgid="1968128556747588800">"Uit"</item>
-    <item msgid="3033215374382962216">"Gedeeltes met overbelasting weergeven"</item>
-    <item msgid="3474333938380896988">"Gebieden voor deuteranomalie weergeven"</item>
+    <item msgid="3033215374382962216">"Gedeelten met overbelasting tonen"</item>
+    <item msgid="3474333938380896988">"Gebieden voor deuteranomalie tonen"</item>
   </string-array>
   <string-array name="app_process_limit_entries">
     <item msgid="794656271086646068">"Standaardlimiet"</item>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index a3a490d..e5e03e1 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -309,7 +309,7 @@
     <string name="dev_settings_warning_message" msgid="37741686486073668">"Deze instellingen zijn uitsluitend bedoeld voor ontwikkelingsgebruik. Je apparaat en apps kunnen hierdoor vastlopen of anders reageren."</string>
     <string name="verify_apps_over_usb_title" msgid="6031809675604442636">"Apps verifiëren via USB"</string>
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Apps die zijn geïnstalleerd via ADB/ADT, controleren op schadelijk gedrag"</string>
-    <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth-apparaten zonder namen (alleen MAC-adressen) worden weergegeven"</string>
+    <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth-apparaten zonder naam (alleen MAC-adressen) worden weergegeven"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Hiermee wordt de functie voor absoluut volume van Bluetooth uitgeschakeld in geval van volumeproblemen met externe apparaten, zoals een onacceptabel hoog volume of geen volumeregeling."</string>
     <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Hierdoor wordt de Gabeldorsche-functiestack voor bluetooth ingeschakeld."</string>
     <string name="enhanced_connectivity_summary" msgid="1576414159820676330">"Hiermee wordt de functie voor verbeterde connectiviteit ingeschakeld."</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index f1d4a8a..9ccfb80 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -424,8 +424,7 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (ਲਾਲ-ਹਰਾ)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (ਨੀਲਾ-ਪੀਲਾ)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ਰੰਗ ਸੁਧਾਈ"</string>
-    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (2333641630205214702) -->
-    <skip />
+    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"ਆਪਣੇ ਡੀਵਾਈਸ \'ਤੇ ਰੰਗਾਂ ਨੂੰ ਦਿਖਾਉਣ ਦੇ ਤਰੀਕੇ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ। ਇਹ ਉਦੋਂ ਲਾਹੇਵੰਦ ਹੋ ਸਕਦਾ ਹੈ ਜਦੋਂ ਤੁਸੀਂ ਇਹ ਕਰਨਾ ਚਾਹੋਗੇ:&lt;br/&gt;&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;ਰੰਗਾਂ ਨੂੰ ਹੋਰ ਸਟੀਕਤਾ ਨਾਲ ਦੇਖਣਾ&lt;/li&gt; &lt;li&gt;&amp;nbsp;ਫੋਕਸ ਕਰਨ ਵਿੱਚ ਤੁਹਾਡੀ ਮਦਦ ਲਈ ਰੰਗਾਂ ਨੂੰ ਹਟਾਉਣਾ&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ਦੁਆਰਾ ਓਵਰਰਾਈਡ ਕੀਤਾ"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"ਲਗਭਗ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ਬਾਕੀ"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 95ba023..f667c6a 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -424,8 +424,7 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (e kuqe - e gjelbër)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (e kaltër - e verdhë)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korrigjimi i ngjyrës"</string>
-    <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (2333641630205214702) -->
-    <skip />
+    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"Rregullo mënyrën se si shfaqen ngjyrat në pajisjen tënde. Kjo mund të jetë e dobishme kur dëshiron që:&lt;br/&gt;&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;T\'i shikosh ngjyrat me më shumë saktësi&lt;/li&gt; &lt;li&gt;&amp;nbsp;T\'i heqësh ngjyrat për të të ndihmuar të fokusohesh&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Mbivendosur nga <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Rreth <xliff:g id="TIME_REMAINING">%1$s</xliff:g> të mbetura"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 0b03d99..4c35d2f 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -424,7 +424,7 @@
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"‏Protanomaly (سرخ سبز)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"‏Tritanomaly (نیلا پیلا)"</string>
     <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"رنگ کی اصلاح"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"‏آپ کے آلے پر رنگوں کے ڈسپلے ہونے کے طریقے کو ایڈجسٹ کریں۔ یہ درج ذیل کے لیے مددگار ثابت ہوسکتا ہے ‎:&lt;br/&gt;&amp;ltlt;br/&gt; ‎&lt;ol&gt;‎&lt;li&gt;‎جب آپ رنگوں کو مزید درست طریقے سے دیکھنا چاہیں ‎&lt;/li&gt; &lt;li&gt;&amp;nbsp;‎فوکس کرنے میں مدد کرنے کے لئے رنگوں کو ہٹائیں ‎&lt;/li&gt; &lt;/ol&gt;‎"</string>
+    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"‏آپ کے آلے پر رنگوں کے ڈسپلے ہونے کے طریقے کو ایڈجسٹ کریں۔ یہ درج ذیل کے لیے مددگار ثابت ہوسکتا ہے ‎:&lt;br/&gt;&amp;ltlt;br/&gt; ‎&lt;ol&gt;‎&lt;li&gt;‎جب آپ رنگوں کو مزید درست طریقے سے دیکھنا چاہیں ‎&lt;/li&gt; &lt;li&gt;&amp;nbsp;‎فوکس کرنے میں مدد کرنے کے لئے رنگوں کو ہٹانا چاہیں ‎&lt;/li&gt; &lt;/ol&gt;‎"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> کے ذریعہ منسوخ کردیا گیا"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> باقی ہے"</string>
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 5d4078d..fbb84fd 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -242,7 +242,7 @@
     <bool name="def_hdmiControlAutoDeviceOff">true</bool>
 
     <!-- Default for Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED -->
-    <bool name="def_swipe_bottom_to_notification_enabled">true</bool>
+    <bool name="def_swipe_bottom_to_notification_enabled">false</bool>
 
     <!-- Default for Settings.Secure.ONE_HANDED_MODE_ENABLED -->
     <bool name="def_one_handed_mode_enabled">false</bool>
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b86ae6d..2b4fef0 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -125,7 +125,6 @@
     <uses-permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS" />
     <uses-permission android:name="android.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS" />
     <uses-permission android:name="android.permission.CLEAR_FREEZE_PERIOD" />
-    <uses-permission android:name="android.permission.QUERY_USERS" />
     <uses-permission android:name="android.permission.MODIFY_QUIET_MODE" />
     <uses-permission android:name="android.permission.ACCESS_LOWPAN_STATE"/>
     <uses-permission android:name="android.permission.CHANGE_LOWPAN_STATE"/>
@@ -438,6 +437,9 @@
     <!-- Permission required for CTS test - ResourceObserverNativeTest -->
     <uses-permission android:name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER" />
 
+    <!-- Permission required for CTS test - android.widget.cts.ToastTest -->
+    <uses-permission android:name="android.permission.UNLIMITED_TOASTS" />
+
     <application android:label="@string/app_label"
                 android:theme="@android:style/Theme.DeviceDefault.DayNight"
                 android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/res/layout/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml
index 19bcf95..6d44138c 100644
--- a/packages/SystemUI/res/layout/long_screenshot.xml
+++ b/packages/SystemUI/res/layout/long_screenshot.xml
@@ -48,10 +48,10 @@
 
     <ImageView
         android:id="@+id/preview"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
+        android:layout_width="0px"
+        android:layout_height="0px"
         android:layout_marginBottom="42dp"
-        android:layout_marginHorizontal="48dp"
+        android:paddingHorizontal="48dp"
         app:layout_constrainedHeight="true"
         app:layout_constrainedWidth="true"
         app:layout_constraintTop_toBottomOf="@id/save"
@@ -64,8 +64,8 @@
 
     <com.android.systemui.screenshot.CropView
         android:id="@+id/crop_view"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
+        android:layout_width="0px"
+        android:layout_height="0px"
         android:layout_marginBottom="42dp"
         app:layout_constrainedHeight="true"
         app:layout_constrainedWidth="true"
diff --git a/packages/SystemUI/res/layout/people_space_small_view.xml b/packages/SystemUI/res/layout/people_tile_large_empty.xml
similarity index 63%
copy from packages/SystemUI/res/layout/people_space_small_view.xml
copy to packages/SystemUI/res/layout/people_tile_large_empty.xml
index 7b1abae..1e00307 100644
--- a/packages/SystemUI/res/layout/people_space_small_view.xml
+++ b/packages/SystemUI/res/layout/people_tile_large_empty.xml
@@ -20,40 +20,48 @@
     <LinearLayout
         android:id="@+id/item"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
+        android:layout_height="match_parent"
         android:layout_gravity="center"
+        android:gravity="center"
         android:background="@drawable/people_space_tile_view_card"
         android:orientation="vertical"
-        android:paddingHorizontal="4dp"
-        android:paddingVertical="8dp">
+        android:paddingHorizontal="16dp"
+        android:paddingVertical="16dp">
 
         <ImageView
             android:id="@+id/person_icon"
             android:layout_gravity="center"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_weight="1" />
-
-        <ImageView
-            android:id="@+id/predefined_icon"
-            android:layout_gravity="center"
-            android:paddingTop="4dp"
-            android:layout_width="18dp"
-            android:layout_height="22dp"
-            android:layout_weight="1" />
+            android:layout_height="wrap_content" />
 
         <TextView
             android:id="@+id/name"
             android:layout_gravity="center"
-            android:paddingTop="4dp"
+            android:paddingTop="14dp"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_weight="1"
             android:ellipsize="end"
-            android:maxLines="1"
-            android:paddingHorizontal="8dp"
+            android:singleLine="true"
             android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
             android:textColor="?android:attr/textColorPrimary"
-            android:textSize="14sp" />
+            android:textSize="16sp" />
+        <TextView
+            android:id="@+id/last_interaction"
+            android:layout_gravity="center"
+            android:text="@string/empty_status"
+            android:textColor="?android:attr/textColorSecondary"
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+            android:textSize="14sp"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:singleLine="true"/>
+        <ImageView
+            android:id="@+id/availability"
+            android:layout_gravity="center"
+            android:layout_width="10dp"
+            android:layout_height="26dp"
+            android:paddingTop="16dp"
+            android:background="@drawable/circle_green_10dp"/>
     </LinearLayout>
 </FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_tile_large_with_content.xml b/packages/SystemUI/res/layout/people_tile_large_with_content.xml
new file mode 100644
index 0000000..f2341b5
--- /dev/null
+++ b/packages/SystemUI/res/layout/people_tile_large_with_content.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~      http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/item"
+    android:background="@drawable/people_space_tile_view_card"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center"
+    android:padding="16dp"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="start|top"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@+id/person_icon"
+            android:layout_marginStart="-2dp"
+            android:layout_marginTop="-2dp"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1" />
+
+        <ImageView
+            android:id="@+id/availability"
+            android:layout_marginStart="-2dp"
+            android:layout_width="10dp"
+            android:layout_height="10dp"
+            android:background="@drawable/circle_green_10dp" />
+    </LinearLayout>
+
+    <TextView
+        android:layout_gravity="center"
+        android:id="@+id/name"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingBottom="12dp"
+        android:gravity="start"
+        android:singleLine="true"
+        android:ellipsize="end"
+        android:text="@string/empty_user_name"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textSize="14sp" />
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingBottom="4dp"
+        android:gravity="center_vertical"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@+id/predefined_icon"
+            android:gravity="start|center_vertical"
+            android:paddingEnd="6dp"
+            android:layout_width="24dp"
+            android:layout_height="18dp" />
+
+        <TextView
+            android:layout_gravity="center"
+            android:id="@+id/subtext"
+            android:gravity="center_vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:singleLine="true"
+            android:text="@string/empty_user_name"
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+            android:textColor="?android:attr/textColorSecondary"
+            android:textSize="12sp" />
+    </LinearLayout>
+
+    <ImageView
+        android:id="@+id/image"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@drawable/people_space_content_background"
+        android:gravity="center"
+        android:scaleType="centerCrop" />
+
+    <TextView
+        android:layout_gravity="center"
+        android:id="@+id/text_content"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:singleLine="false"
+        android:text="@string/empty_status"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textSize="12sp" />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/people_space_large_avatar_tile.xml b/packages/SystemUI/res/layout/people_tile_medium_empty.xml
similarity index 94%
rename from packages/SystemUI/res/layout/people_space_large_avatar_tile.xml
rename to packages/SystemUI/res/layout/people_tile_medium_empty.xml
index b1a37bf..c35787e 100644
--- a/packages/SystemUI/res/layout/people_space_large_avatar_tile.xml
+++ b/packages/SystemUI/res/layout/people_tile_medium_empty.xml
@@ -35,16 +35,17 @@
             android:layout_height="match_parent">
             <ImageView
                 android:id="@+id/person_icon"
-                android:layout_width="60dp"
-                android:layout_height="60dp"/>
+                android:layout_width="64dp"
+                android:layout_height="64dp"/>
             <ImageView
                 android:id="@+id/availability"
+                android:layout_marginStart="-2dp"
                 android:layout_width="10dp"
                 android:layout_height="10dp"
                 android:background="@drawable/circle_green_10dp"/>
             <LinearLayout
                 android:orientation="vertical"
-                android:paddingStart="4dp"
+                android:paddingStart="6dp"
                 android:gravity="top"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content">
diff --git a/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
similarity index 71%
rename from packages/SystemUI/res/layout/people_space_small_avatar_tile.xml
rename to packages/SystemUI/res/layout/people_tile_medium_with_content.xml
index 6a606e1..e4e4cd8 100644
--- a/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml
+++ b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
@@ -27,28 +27,37 @@
         android:layout_gravity="center"
         android:padding="8dp"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content">
+        android:layout_height="match_parent">
+
         <LinearLayout
             android:orientation="horizontal"
             android:gravity="top"
+            android:layout_weight="1"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content">
-                <ImageView
-                    android:gravity="start"
-                    android:id="@+id/person_icon"
-                    android:layout_width="56dp"
-                    android:layout_height="56dp"/>
+            android:layout_height="0dp">
+
+            <ImageView
+                android:gravity="start"
+                android:id="@+id/person_icon"
+                android:layout_marginStart="-2dp"
+                android:layout_marginTop="-2dp"
+                android:layout_width="52dp"
+                android:layout_height="52dp" />
+
             <ImageView
                 android:id="@+id/availability"
+                android:layout_marginStart="-2dp"
                 android:layout_width="10dp"
                 android:layout_height="10dp"
-                android:background="@drawable/circle_green_10dp"/>
+                android:background="@drawable/circle_green_10dp" />
+
             <LinearLayout
                 android:orientation="vertical"
                 android:gravity="top|start"
                 android:paddingStart="12dp"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content">
+
                 <TextView
                     android:id="@+id/subtext"
                     android:text="@string/empty_user_name"
@@ -58,38 +67,36 @@
                     android:paddingBottom="4dp"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:ellipsize="end"/>
-            <LinearLayout
-                android:id="@+id/content_background"
-                android:background="@drawable/people_space_content_background"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent">
+                    android:singleLine="true"
+                    android:ellipsize="end" />
+
                 <ImageView
                     android:id="@+id/image"
                     android:gravity="center"
                     android:background="@drawable/people_space_content_background"
                     android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:scaleType="centerCrop" />
+
+                <TextView
+                    android:id="@+id/text_content"
+                    android:text="@string/empty_status"
+                    android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+                    android:textColor="?android:attr/textColorPrimary"
+                    android:textSize="12sp"
+                    android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:scaleType="centerCrop"/>
-            </LinearLayout>
-            <TextView
-                android:id="@+id/text_content"
-                android:text="@string/empty_status"
-                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
-                android:textColor="?android:attr/textColorPrimary"
-                android:textSize="12sp"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:maxLines="2"
-                android:singleLine="false"
-                android:ellipsize="end"/>
+                    android:maxLines="2"
+                    android:singleLine="false"
+                    android:ellipsize="end" />
             </LinearLayout>
         </LinearLayout>
+
         <LinearLayout
             android:gravity="bottom"
+            android:layout_gravity="center_vertical"
             android:orientation="horizontal"
             android:paddingTop="4dp"
-            android:layout_weight="1"
             android:layout_width="match_parent"
             android:layout_height="wrap_content">
 
@@ -101,16 +108,17 @@
                 android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
                 android:textColor="?android:attr/textColorPrimary"
                 android:textSize="14sp"
-                android:maxLines="1"
+                android:singleLine="true"
                 android:ellipsize="end"
                 android:layout_width="wrap_content"
-                android:layout_height="wrap_content"/>
+                android:layout_height="wrap_content" />
+
             <ImageView
                 android:id="@+id/predefined_icon"
-                android:gravity="end"
+                android:gravity="end|center_vertical"
                 android:paddingStart="6dp"
                 android:layout_width="24dp"
-                android:layout_height="18dp"/>
+                android:layout_height="18dp" />
         </LinearLayout>
     </LinearLayout>
 </LinearLayout>
diff --git a/packages/SystemUI/res/layout/people_space_small_view.xml b/packages/SystemUI/res/layout/people_tile_small.xml
similarity index 94%
rename from packages/SystemUI/res/layout/people_space_small_view.xml
rename to packages/SystemUI/res/layout/people_tile_small.xml
index 7b1abae..914ee3c 100644
--- a/packages/SystemUI/res/layout/people_space_small_view.xml
+++ b/packages/SystemUI/res/layout/people_tile_small.xml
@@ -25,7 +25,8 @@
         android:background="@drawable/people_space_tile_view_card"
         android:orientation="vertical"
         android:paddingHorizontal="4dp"
-        android:paddingVertical="8dp">
+        android:paddingTop="6dp"
+        android:paddingBottom="8dp">
 
         <ImageView
             android:id="@+id/person_icon"
@@ -51,7 +52,7 @@
             android:layout_weight="1"
             android:ellipsize="end"
             android:maxLines="1"
-            android:paddingHorizontal="8dp"
+            android:paddingHorizontal="4dp"
             android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
             android:textColor="?android:attr/textColorPrimary"
             android:textSize="14sp" />
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 0076c51..a773b22 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -202,7 +202,7 @@
     <color name="global_screenshot_background_protection_start">#40000000</color> <!-- 25% black -->
 
     <!-- Long screenshot UI -->
-    <color name="screenshot_crop_scrim">#9444</color>
+    <color name="screenshot_crop_scrim">#6444</color>
 
     <!-- GM2 colors -->
     <color name="GM2_grey_50">#F8F9FA</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b050945..ff4e8e0 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -334,7 +334,6 @@
     <dimen name="screenshot_action_container_corner_radius">10dp</dimen>
     <dimen name="screenshot_action_container_padding_vertical">6dp</dimen>
     <dimen name="screenshot_action_container_margin_horizontal">8dp</dimen>
-    <dimen name="screenshot_action_container_padding_left">96dp</dimen>
     <dimen name="screenshot_action_container_padding_right">8dp</dimen>
     <!-- Radius of the chip background on global screenshot actions -->
     <dimen name="screenshot_button_corner_radius">20dp</dimen>
@@ -1355,6 +1354,18 @@
     <dimen name="people_space_widget_radius">28dp</dimen>
     <dimen name="people_space_image_radius">20dp</dimen>
     <dimen name="people_space_widget_background_padding">6dp</dimen>
+    <dimen name="required_width_for_medium">146dp</dimen>
+    <dimen name="required_width_for_large">138dp</dimen>
+    <dimen name="required_height_for_large">182dp</dimen>
+    <dimen name="default_width">146dp</dimen>
+    <dimen name="default_height">92dp</dimen>
+    <dimen name="avatar_size_for_medium">52dp</dimen>
+    <dimen name="max_people_avatar_size_for_large_content">64dp</dimen>
+    <dimen name="max_people_avatar_size">108dp</dimen>
+    <dimen name="name_text_size_for_small">14sp</dimen>
+    <dimen name="name_text_size_for_large">24sp</dimen>
+    <dimen name="content_text_size_for_medium">12sp</dimen>
+    <dimen name="content_text_size_for_large">14sp</dimen>
 
     <!-- Accessibility floating menu -->
     <dimen name="accessibility_floating_menu_elevation">5dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index b1e1434..d52a251 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -22,8 +22,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
-import android.net.ConnectivityManager;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
 import android.telephony.PhoneStateListener;
@@ -179,8 +179,8 @@
         mBgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
         mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         mBgHandler.post(() -> {
-            boolean supported = ConnectivityManager.from(mContext).isNetworkSupported(
-                    ConnectivityManager.TYPE_MOBILE);
+            boolean supported =
+                    mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
             if (supported && mNetworkSupported.compareAndSet(false, supported)) {
                 // This will set/remove the listeners appropriately. Note that it will never double
                 // add the listeners.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
index bb4038e..b4858f4 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
@@ -18,10 +18,6 @@
 
 import static android.view.View.GONE;
 
-import static com.android.systemui.accessibility.floatingmenu.AccessibilityTargetAdapter.ItemType.FIRST_ITEM;
-import static com.android.systemui.accessibility.floatingmenu.AccessibilityTargetAdapter.ItemType.LAST_ITEM;
-import static com.android.systemui.accessibility.floatingmenu.AccessibilityTargetAdapter.ItemType.REGULAR_ITEM;
-
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -47,16 +43,16 @@
     private final List<AccessibilityTarget> mTargets;
 
     @IntDef({
-            FIRST_ITEM,
-            REGULAR_ITEM,
-            LAST_ITEM
+            AccessibilityTargetAdapter.FIRST_ITEM,
+            AccessibilityTargetAdapter.REGULAR_ITEM,
+            AccessibilityTargetAdapter.LAST_ITEM
     })
     @Retention(RetentionPolicy.SOURCE)
-    @interface ItemType {
-        int FIRST_ITEM = 0;
-        int REGULAR_ITEM = 1;
-        int LAST_ITEM = 2;
-    }
+    @interface ItemType {}
+
+    private static final int FIRST_ITEM = 0;
+    private static final int REGULAR_ITEM = 1;
+    private static final int LAST_ITEM = 2;
 
     public AccessibilityTargetAdapter(List<AccessibilityTarget> targets) {
         mTargets = targets;
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index d3168c8..a7c1451 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -14,14 +14,8 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.PixelFormat;
 import android.metrics.LogMaker;
 import android.os.AsyncTask;
-import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -30,12 +24,6 @@
 import android.provider.Settings;
 import android.service.voice.VoiceInteractionSession;
 import android.util.Log;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.widget.ImageView;
 
 import com.android.internal.app.AssistUtils;
 import com.android.internal.app.IVoiceInteractionSessionListener;
@@ -43,7 +31,6 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.settingslib.applications.InterestingConfigChanges;
 import com.android.systemui.R;
 import com.android.systemui.assist.ui.DefaultUiController;
 import com.android.systemui.dagger.SysUISingleton;
@@ -97,8 +84,6 @@
     // Note that VERBOSE logging may leak PII (e.g. transcription contents).
     private static final boolean VERBOSE = false;
 
-    private static final String ASSIST_ICON_METADATA_NAME =
-            "com.android.systemui.action_assist_icon";
     private static final String INVOCATION_TIME_MS_KEY = "invocation_time_ms";
     private static final String INVOCATION_PHONE_STATE_KEY = "invocation_phone_state";
     public static final String INVOCATION_TYPE_KEY = "invocation_type";
@@ -123,69 +108,29 @@
     private static final long TIMEOUT_ACTIVITY = 1000;
 
     protected final Context mContext;
-    private final WindowManager mWindowManager;
     private final AssistDisclosure mAssistDisclosure;
-    private final InterestingConfigChanges mInterestingConfigChanges;
     private final PhoneStateMonitor mPhoneStateMonitor;
     private final AssistHandleBehaviorController mHandleController;
     private final UiController mUiController;
     protected final Lazy<SysUiState> mSysUiState;
     protected final AssistLogger mAssistLogger;
 
-    private AssistOrbContainer mView;
     private final DeviceProvisionedController mDeviceProvisionedController;
     private final CommandQueue mCommandQueue;
+    private final AssistOrbController mOrbController;
     protected final AssistUtils mAssistUtils;
-    private final boolean mShouldEnableOrb;
 
     private IVoiceInteractionSessionShowCallback mShowCallback =
             new IVoiceInteractionSessionShowCallback.Stub() {
 
                 @Override
                 public void onFailed() throws RemoteException {
-                    mView.post(mHideRunnable);
+                    mOrbController.postHide();
                 }
 
                 @Override
                 public void onShown() throws RemoteException {
-                    mView.post(mHideRunnable);
-                }
-            };
-
-    private Runnable mHideRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mView.removeCallbacks(this);
-            mView.show(false /* show */, true /* animate */);
-        }
-    };
-
-    private ConfigurationController.ConfigurationListener mConfigurationListener =
-            new ConfigurationController.ConfigurationListener() {
-                @Override
-                public void onConfigChanged(Configuration newConfig) {
-                    if (!mInterestingConfigChanges.applyNewConfig(mContext.getResources())) {
-                        return;
-                    }
-                    boolean visible = false;
-                    if (mView != null) {
-                        visible = mView.isShowing();
-                        if (mView.isAttachedToWindow()) {
-                            mWindowManager.removeView(mView);
-                        }
-                    }
-
-                    mView = (AssistOrbContainer) LayoutInflater.from(mContext).inflate(
-                            R.layout.assist_orb, null);
-                    mView.setVisibility(View.GONE);
-                    mView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
-                            | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
-                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
-                    WindowManager.LayoutParams lp = getLayoutParams();
-                    mWindowManager.addView(mView, lp);
-                    if (visible) {
-                        mView.show(true /* show */, false /* animate */);
-                    }
+                    mOrbController.postHide();
                 }
             };
 
@@ -205,21 +150,15 @@
         mContext = context;
         mDeviceProvisionedController = controller;
         mCommandQueue = commandQueue;
-        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
         mAssistUtils = assistUtils;
         mAssistDisclosure = new AssistDisclosure(context, new Handler());
         mPhoneStateMonitor = phoneStateMonitor;
         mHandleController = handleController;
         mAssistLogger = assistLogger;
 
-        configurationController.addCallback(mConfigurationListener);
+        mOrbController = new AssistOrbController(configurationController, context);
 
         registerVoiceInteractionSessionListener();
-        mInterestingConfigChanges = new InterestingConfigChanges(ActivityInfo.CONFIG_ORIENTATION
-                | ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_UI_MODE
-                | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS);
-        mConfigurationListener.onConfigChanged(context.getResources().getConfiguration());
-        mShouldEnableOrb = !ActivityManager.isLowRamDeviceStatic();
 
         mUiController = defaultUiController;
 
@@ -282,7 +221,7 @@
     }
 
     protected boolean shouldShowOrb() {
-        return false;
+        return !ActivityManager.isLowRamDeviceStatic();
     }
 
     public void startAssist(Bundle args) {
@@ -293,10 +232,8 @@
 
         final boolean isService = assistComponent.equals(getVoiceInteractorComponentName());
         if (!isService || (!isVoiceSessionRunning() && shouldShowOrb())) {
-            showOrb(assistComponent, isService);
-            mView.postDelayed(mHideRunnable, isService
-                    ? TIMEOUT_SERVICE
-                    : TIMEOUT_ACTIVITY);
+            mOrbController.showOrb(assistComponent, isService);
+            mOrbController.postHideDelayed(isService ? TIMEOUT_SERVICE : TIMEOUT_ACTIVITY);
         }
 
         if (args == null) {
@@ -340,30 +277,6 @@
         mAssistUtils.hideCurrentSession();
     }
 
-    private WindowManager.LayoutParams getLayoutParams() {
-        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                mContext.getResources().getDimensionPixelSize(R.dimen.assist_orb_scrim_height),
-                WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING,
-                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
-                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
-                PixelFormat.TRANSLUCENT);
-        lp.token = new Binder();
-        lp.gravity = Gravity.BOTTOM | Gravity.START;
-        lp.setTitle("AssistPreviewPanel");
-        lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
-                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
-        return lp;
-    }
-
-    private void showOrb(@NonNull ComponentName assistComponent, boolean isService) {
-        maybeSwapSearchIcon(assistComponent, isService);
-        if (mShouldEnableOrb) {
-            mView.show(true /* show */, true /* animate */);
-        }
-    }
-
     private void startAssistInternal(Bundle args, @NonNull ComponentName assistComponent,
             boolean isService) {
         if (isService) {
@@ -440,44 +353,6 @@
         return mAssistUtils.isSessionRunning();
     }
 
-    private void maybeSwapSearchIcon(@NonNull ComponentName assistComponent, boolean isService) {
-        replaceDrawable(mView.getOrb().getLogo(), assistComponent, ASSIST_ICON_METADATA_NAME,
-                isService);
-    }
-
-    public void replaceDrawable(ImageView v, ComponentName component, String name,
-            boolean isService) {
-        if (component != null) {
-            try {
-                PackageManager packageManager = mContext.getPackageManager();
-                // Look for the search icon specified in the activity meta-data
-                Bundle metaData = isService
-                        ? packageManager.getServiceInfo(
-                        component, PackageManager.GET_META_DATA).metaData
-                        : packageManager.getActivityInfo(
-                                component, PackageManager.GET_META_DATA).metaData;
-                if (metaData != null) {
-                    int iconResId = metaData.getInt(name);
-                    if (iconResId != 0) {
-                        Resources res = packageManager.getResourcesForApplication(
-                                component.getPackageName());
-                        v.setImageDrawable(res.getDrawable(iconResId));
-                        return;
-                    }
-                }
-            } catch (PackageManager.NameNotFoundException e) {
-                if (VERBOSE) {
-                    Log.v(TAG, "Assistant component "
-                            + component.flattenToShortString() + " not found");
-                }
-            } catch (Resources.NotFoundException nfe) {
-                Log.w(TAG, "Failed to swap drawable from "
-                        + component.flattenToShortString(), nfe);
-            }
-        }
-        v.setImageDrawable(null);
-    }
-
     protected AssistHandleBehaviorController getHandleBehaviorController() {
         return mHandleController;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java
index f78436a..fe48c26 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java
@@ -55,14 +55,17 @@
         mOrb = (AssistOrbView) findViewById(R.id.assist_orb);
     }
 
-    public void show(final boolean show, boolean animate) {
+    public void show(final boolean show, boolean animate, Runnable onDone) {
         if (show) {
             if (getVisibility() != View.VISIBLE) {
                 setVisibility(View.VISIBLE);
                 if (animate) {
-                    startEnterAnimation();
+                    startEnterAnimation(onDone);
                 } else {
                     reset();
+                    if (onDone != null) {
+                        onDone.run();
+                    }
                 }
             }
         } else {
@@ -72,10 +75,16 @@
                     public void run() {
                         mAnimatingOut = false;
                         setVisibility(View.GONE);
+                        if (onDone != null) {
+                            onDone.run();
+                        }
                     }
                 });
             } else {
                 setVisibility(View.GONE);
+                if (onDone != null) {
+                    onDone.run();
+                }
             }
         }
     }
@@ -87,7 +96,7 @@
         mNavbarScrim.setAlpha(1f);
     }
 
-    private void startEnterAnimation() {
+    private void startEnterAnimation(Runnable onDone) {
         if (mAnimatingOut) {
             return;
         }
@@ -106,7 +115,8 @@
                         .alpha(1f)
                         .setDuration(300)
                         .setStartDelay(0)
-                        .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+                        .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
+                        .withEndAction(onDone);
             }
         });
     }
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java
new file mode 100644
index 0000000..81a13a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java
@@ -0,0 +1,180 @@
+/*
+ * 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.assist;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.Binder;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+import com.android.settingslib.applications.InterestingConfigChanges;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+
+/**
+ * AssistOrbController controls the showing and hiding of the assistant orb.
+ */
+public class AssistOrbController {
+    private static final String ASSIST_ICON_METADATA_NAME =
+            "com.android.systemui.action_assist_icon";
+    private static final String TAG = "AssistOrbController";
+    private static final boolean VERBOSE = false;
+
+    private final InterestingConfigChanges mInterestingConfigChanges;
+    private AssistOrbContainer mView;
+    private final Context mContext;
+    private final WindowManager mWindowManager;
+
+    private Runnable mHideRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mView.removeCallbacks(this);
+            mView.show(false /* show */, true /* animate */, () -> {
+                mWindowManager.removeView(mView);
+            });
+        }
+    };
+
+    private ConfigurationController.ConfigurationListener mConfigurationListener =
+            new ConfigurationController.ConfigurationListener() {
+                @Override
+                public void onConfigChanged(Configuration newConfig) {
+                    if (!mInterestingConfigChanges.applyNewConfig(mContext.getResources())) {
+                        return;
+                    }
+                    boolean visible = false;
+                    if (mView != null) {
+                        visible = mView.isShowing();
+                        if (mView.isAttachedToWindow()) {
+                            mWindowManager.removeView(mView);
+                        }
+                    }
+
+                    if (visible) {
+                        showOrb(false);
+                    }
+                }
+            };
+
+    AssistOrbController(ConfigurationController configurationController, Context context) {
+        mContext = context;
+        mWindowManager =  mContext.getSystemService(WindowManager.class);
+        mInterestingConfigChanges = new InterestingConfigChanges(ActivityInfo.CONFIG_ORIENTATION
+                | ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_UI_MODE
+                | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS);
+
+        configurationController.addCallback(mConfigurationListener);
+        mConfigurationListener.onConfigChanged(context.getResources().getConfiguration());
+    }
+
+    public void postHide() {
+        mView.post(mHideRunnable);
+    }
+
+    public void postHideDelayed(long delayMs) {
+        mView.postDelayed(mHideRunnable, delayMs);
+    }
+
+    private void showOrb(boolean animated) {
+        if (mView == null) {
+            mView = (AssistOrbContainer) LayoutInflater.from(mContext).inflate(
+                    R.layout.assist_orb, null);
+            mView.setVisibility(View.GONE);
+            mView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+        }
+        if (!mView.isAttachedToWindow()) {
+            WindowManager.LayoutParams params = getLayoutParams();
+            mWindowManager.addView(mView, params);
+        }
+        mView.show(true, animated, null);
+    }
+
+    private WindowManager.LayoutParams getLayoutParams() {
+        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                mContext.getResources().getDimensionPixelSize(R.dimen.assist_orb_scrim_height),
+                WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING,
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+                PixelFormat.TRANSLUCENT);
+        lp.token = new Binder();
+        lp.gravity = Gravity.BOTTOM | Gravity.START;
+        lp.setTitle("AssistPreviewPanel");
+        lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
+                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+        return lp;
+    }
+
+    public void showOrb(@NonNull ComponentName assistComponent, boolean isService) {
+        showOrb(true);
+        maybeSwapSearchIcon(assistComponent, isService);
+    }
+
+    private void maybeSwapSearchIcon(@NonNull ComponentName assistComponent, boolean isService) {
+        replaceDrawable(mView.getOrb().getLogo(), assistComponent, ASSIST_ICON_METADATA_NAME,
+                isService);
+    }
+
+    public void replaceDrawable(ImageView v, ComponentName component, String name,
+            boolean isService) {
+        if (component != null) {
+            try {
+                PackageManager packageManager = mContext.getPackageManager();
+                // Look for the search icon specified in the activity meta-data
+                Bundle metaData = isService
+                        ? packageManager.getServiceInfo(
+                        component, PackageManager.GET_META_DATA).metaData
+                        : packageManager.getActivityInfo(
+                                component, PackageManager.GET_META_DATA).metaData;
+                if (metaData != null) {
+                    int iconResId = metaData.getInt(name);
+                    if (iconResId != 0) {
+                        Resources res = packageManager.getResourcesForApplication(
+                                component.getPackageName());
+                        v.setImageDrawable(res.getDrawable(iconResId));
+                        return;
+                    }
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                if (VERBOSE) {
+                    Log.v(TAG, "Assistant component "
+                            + component.flattenToShortString() + " not found");
+                }
+            } catch (Resources.NotFoundException nfe) {
+                Log.w(TAG, "Failed to swap drawable from "
+                        + component.flattenToShortString(), nfe);
+            }
+        }
+        v.setImageDrawable(null);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 08e5d56..1f652db 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -33,6 +33,7 @@
     @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
 
     private boolean mForceShow;
+    private boolean mQsExpanded;
 
     protected UdfpsKeyguardViewController(
             @NonNull UdfpsKeyguardView view,
@@ -64,7 +65,7 @@
     protected void onViewDetached() {
         super.onViewDetached();
         mStatusBarStateController.removeCallback(mStateListener);
-        mAlternateAuthInterceptor.reset();
+        mAlternateAuthInterceptor.resetForceShow();
         mKeyguardViewManager.setAlternateAuthInterceptor(null);
     }
 
@@ -100,6 +101,11 @@
         if (mForceShow) {
             return false;
         }
+
+        if (mQsExpanded) {
+            return true;
+        }
+
         return super.shouldPauseAuth();
     }
 
@@ -130,7 +136,7 @@
                 }
 
                 @Override
-                public boolean reset() {
+                public boolean resetForceShow() {
                     if (!mForceShow) {
                         return false;
                     }
@@ -140,7 +146,7 @@
                 }
 
                 @Override
-                public boolean isShowingAlternativeAuth() {
+                public boolean isShowingAlternateAuth() {
                     return mForceShow;
                 }
 
@@ -150,6 +156,12 @@
                 }
 
                 @Override
+                public void setQsExpanded(boolean expanded) {
+                    mQsExpanded = expanded;
+                    updatePauseAuth();
+                }
+
+                @Override
                 public void dump(PrintWriter pw) {
                     pw.print(getTag());
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 461a730..553e5a7 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -50,6 +50,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
@@ -356,7 +357,8 @@
         filter.addAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
         mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter);
 
-        mHasTelephony = connectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+        mHasTelephony =
+                context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
 
         // get notified of phone state changes
         telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index f9c2a2a..93ce5a8 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -18,42 +18,23 @@
 
 import static android.app.Notification.CATEGORY_MISSED_CALL;
 import static android.app.Notification.EXTRA_MESSAGES;
-import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY;
-import static android.app.people.ConversationStatus.ACTIVITY_AUDIO;
-import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
-import static android.app.people.ConversationStatus.ACTIVITY_GAME;
-import static android.app.people.ConversationStatus.ACTIVITY_LOCATION;
-import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
-import static android.app.people.ConversationStatus.ACTIVITY_UPCOMING_BIRTHDAY;
-import static android.app.people.ConversationStatus.ACTIVITY_VIDEO;
-import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
-import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT;
-import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH;
-import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT;
-import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH;
 
-import android.annotation.Nullable;
 import android.app.INotificationManager;
 import android.app.Notification;
-import android.app.PendingIntent;
 import android.app.people.ConversationChannel;
-import android.app.people.ConversationStatus;
 import android.app.people.IPeopleManager;
 import android.app.people.PeopleSpaceTile;
 import android.appwidget.AppWidgetManager;
 import android.content.Context;
-import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
-import android.content.res.Configuration;
 import android.database.Cursor;
 import android.database.SQLException;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
 import android.icu.text.MeasureFormat;
 import android.icu.util.Measure;
 import android.icu.util.MeasureUnit;
@@ -66,10 +47,7 @@
 import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
-import android.util.IconDrawableFactory;
 import android.util.Log;
-import android.util.TypedValue;
-import android.view.View;
 import android.widget.RemoteViews;
 
 import androidx.preference.PreferenceManager;
@@ -81,8 +59,6 @@
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.R;
 import com.android.systemui.people.widget.AppWidgetOptionsHelper;
-import com.android.systemui.people.widget.LaunchConversationActivity;
-import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
 import com.android.systemui.people.widget.PeopleTileKey;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -99,10 +75,7 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Optional;
 import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -123,18 +96,6 @@
 
     public static final PeopleTileKey EMPTY_KEY =
             new PeopleTileKey(EMPTY_STRING, INVALID_USER_ID, EMPTY_STRING);
-    public static final int SMALL_LAYOUT = 0;
-    public static final int MEDIUM_LAYOUT = 1;
-    @VisibleForTesting
-    static final int REQUIRED_WIDTH_FOR_MEDIUM = 146;
-    private static final int AVATAR_SIZE_FOR_MEDIUM = 56;
-    private static final int DEFAULT_WIDTH = 146;
-    private static final int DEFAULT_HEIGHT = 92;
-
-    private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+");
-    private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+");
-    private static final Pattern ANY_DOUBLE_MARK_PATTERN = Pattern.compile("[!?][!?]+");
-    private static final Pattern MIXED_MARK_PATTERN = Pattern.compile("![?].*|.*[?]!");
 
     /** Represents whether {@link StatusBarNotification} was posted or removed. */
     public enum NotificationAction {
@@ -336,363 +297,6 @@
                 .build();
     }
 
-    /** Creates a {@link RemoteViews} for {@code tile}. */
-    public static RemoteViews createRemoteViews(Context context,
-            PeopleSpaceTile tile, int appWidgetId, Bundle options) {
-        int layoutSize = getLayoutSize(context, options);
-        RemoteViews viewsForTile = getViewForTile(context, tile, layoutSize);
-        int maxAvatarSize = getMaxAvatarSize(context, options, layoutSize);
-        RemoteViews views = setCommonRemoteViewsFields(context, viewsForTile, tile, maxAvatarSize);
-        return setLaunchIntents(context, views, tile, appWidgetId);
-    }
-
-    /**
-     * The prioritization for the {@code tile} content is missed calls, followed by notification
-     * content, then birthdays, then the most recent status, and finally last interaction.
-     */
-    private static RemoteViews getViewForTile(Context context, PeopleSpaceTile tile,
-            int layoutSize) {
-        if (Objects.equals(tile.getNotificationCategory(), CATEGORY_MISSED_CALL)) {
-            if (DEBUG) Log.d(TAG, "Create missed call view");
-            return createMissedCallRemoteViews(context, tile, layoutSize);
-        }
-
-        if (tile.getNotificationKey() != null) {
-            if (DEBUG) Log.d(TAG, "Create notification view");
-            return createNotificationRemoteViews(context, tile, layoutSize);
-        }
-
-        // TODO: Add sorting when we expose timestamp of statuses.
-        List<ConversationStatus> statusesForEntireView =
-                tile.getStatuses() == null ? Arrays.asList() : tile.getStatuses().stream().filter(
-                        c -> isStatusValidForEntireStatusView(c)).collect(Collectors.toList());
-        ConversationStatus birthdayStatus = getBirthdayStatus(tile, statusesForEntireView);
-        if (birthdayStatus != null) {
-            if (DEBUG) Log.d(TAG, "Create birthday view");
-            return createStatusRemoteViews(context, birthdayStatus, layoutSize);
-        }
-
-        if (!statusesForEntireView.isEmpty()) {
-            if (DEBUG) {
-                Log.d(TAG,
-                        "Create status view for: " + statusesForEntireView.get(0).getActivity());
-            }
-            return createStatusRemoteViews(context, statusesForEntireView.get(0), layoutSize);
-        }
-
-        return createLastInteractionRemoteViews(context, tile, layoutSize);
-    }
-
-    /** Calculates the best layout relative to the size in {@code options}. */
-    private static int getLayoutSize(Context context, Bundle options) {
-        int display = context.getResources().getConfiguration().orientation;
-        int width = display == Configuration.ORIENTATION_PORTRAIT
-                ? options.getInt(OPTION_APPWIDGET_MIN_WIDTH, DEFAULT_WIDTH) : options.getInt(
-                OPTION_APPWIDGET_MAX_WIDTH, DEFAULT_WIDTH);
-        int height = display == Configuration.ORIENTATION_PORTRAIT ? options.getInt(
-                OPTION_APPWIDGET_MAX_HEIGHT, DEFAULT_HEIGHT)
-                : options.getInt(OPTION_APPWIDGET_MIN_HEIGHT, DEFAULT_HEIGHT);
-        // Small layout used below a certain minimum width with any height.
-        if (width < REQUIRED_WIDTH_FOR_MEDIUM) {
-            if (DEBUG) Log.d(TAG, "Small view for width: " + width + " height: " + height);
-            return SMALL_LAYOUT;
-        }
-        if (DEBUG) Log.d(TAG, "Medium view for width: " + width + " height: " + height);
-        return MEDIUM_LAYOUT;
-    }
-
-    /** Returns the max avatar size for {@code layoutSize} under the current {@code options}. */
-    private static int getMaxAvatarSize(Context context, Bundle options, int layoutSize) {
-        int avatarHeightSpace = AVATAR_SIZE_FOR_MEDIUM;
-        int avatarWidthSpace = AVATAR_SIZE_FOR_MEDIUM;
-
-        if (layoutSize == SMALL_LAYOUT) {
-            int display = context.getResources().getConfiguration().orientation;
-            int width = display == Configuration.ORIENTATION_PORTRAIT
-                    ? options.getInt(OPTION_APPWIDGET_MIN_WIDTH, DEFAULT_WIDTH) : options.getInt(
-                    OPTION_APPWIDGET_MAX_WIDTH, DEFAULT_WIDTH);
-            int height = display == Configuration.ORIENTATION_PORTRAIT ? options.getInt(
-                    OPTION_APPWIDGET_MAX_HEIGHT, DEFAULT_HEIGHT)
-                    : options.getInt(OPTION_APPWIDGET_MIN_HEIGHT, DEFAULT_HEIGHT);
-            avatarHeightSpace = height - (8 + 4 + 18 + 8);
-            avatarWidthSpace = width - (4 + 4);
-        }
-        if (DEBUG) Log.d(TAG, "Height: " + avatarHeightSpace + " width: " + avatarWidthSpace);
-        return Math.min(avatarHeightSpace, avatarWidthSpace);
-    }
-
-    @Nullable
-    private static ConversationStatus getBirthdayStatus(PeopleSpaceTile tile,
-            List<ConversationStatus> statuses) {
-        Optional<ConversationStatus> birthdayStatus = statuses.stream().filter(
-                c -> c.getActivity() == ACTIVITY_BIRTHDAY).findFirst();
-        if (birthdayStatus.isPresent()) {
-            return birthdayStatus.get();
-        }
-        if (!TextUtils.isEmpty(tile.getBirthdayText())) {
-            return new ConversationStatus.Builder(tile.getId(), ACTIVITY_BIRTHDAY).build();
-        }
-
-        return null;
-    }
-
-    /**
-     * Returns whether a {@code status} should have its own entire templated view.
-     *
-     * <p>A status may still be shown on the view (for example, as a new story ring) even if it's
-     * not valid to compose an entire view.
-     */
-    private static boolean isStatusValidForEntireStatusView(ConversationStatus status) {
-        switch (status.getActivity()) {
-            // Birthday & Anniversary don't require text provided or icon provided.
-            case ACTIVITY_BIRTHDAY:
-            case ACTIVITY_ANNIVERSARY:
-                return true;
-            default:
-                // For future birthday, location, new story, video, music, game, and other, the
-                // app must provide either text or an icon.
-                return !TextUtils.isEmpty(status.getDescription())
-                        || status.getIcon() != null;
-        }
-    }
-
-    private static RemoteViews createStatusRemoteViews(Context context, ConversationStatus status,
-            int layoutSize) {
-        int layout = layoutSize == SMALL_LAYOUT ? R.layout.people_space_small_view
-                : R.layout.people_space_small_avatar_tile;
-        RemoteViews views = new RemoteViews(context.getPackageName(), layout);
-        CharSequence statusText = status.getDescription();
-        if (TextUtils.isEmpty(statusText)) {
-            statusText = getStatusTextByType(context, status.getActivity());
-        }
-        views.setViewVisibility(R.id.subtext, View.GONE);
-        views.setViewVisibility(R.id.text_content, View.VISIBLE);
-        TypedValue typedValue = new TypedValue();
-        context.getTheme().resolveAttribute(android.R.attr.textColorSecondary, typedValue, true);
-        int secondaryTextColor = context.getColor(typedValue.resourceId);
-        views.setInt(R.id.text_content, "setTextColor", secondaryTextColor);
-        views.setTextViewText(R.id.text_content, statusText);
-        Icon statusIcon = status.getIcon();
-        if (statusIcon != null) {
-            views.setImageViewIcon(R.id.image, statusIcon);
-            views.setBoolean(R.id.content_background, "setClipToOutline", true);
-        } else {
-            views.setViewVisibility(R.id.content_background, View.GONE);
-        }
-        // TODO: Set status pre-defined icons
-        views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_person);
-        ensurePredefinedIconVisibleOnSmallView(views, layoutSize);
-        return views;
-    }
-
-    private static String getStatusTextByType(Context context, int activity) {
-        switch (activity) {
-            case ACTIVITY_BIRTHDAY:
-                return context.getString(R.string.birthday_status);
-            case ACTIVITY_UPCOMING_BIRTHDAY:
-                return context.getString(R.string.upcoming_birthday_status);
-            case ACTIVITY_ANNIVERSARY:
-                return context.getString(R.string.anniversary_status);
-            case ACTIVITY_LOCATION:
-                return context.getString(R.string.location_status);
-            case ACTIVITY_NEW_STORY:
-                return context.getString(R.string.new_story_status);
-            case ACTIVITY_VIDEO:
-                return context.getString(R.string.video_status);
-            case ACTIVITY_AUDIO:
-                return context.getString(R.string.audio_status);
-            case ACTIVITY_GAME:
-                return context.getString(R.string.game_status);
-            default:
-                return EMPTY_STRING;
-        }
-    }
-
-    private static RemoteViews setCommonRemoteViewsFields(Context context, RemoteViews views,
-            PeopleSpaceTile tile, int maxAvatarSize) {
-        try {
-            boolean isAvailable =
-                    tile.getStatuses() != null && tile.getStatuses().stream().anyMatch(
-                            c -> c.getAvailability() == AVAILABILITY_AVAILABLE);
-            if (isAvailable) {
-                views.setViewVisibility(R.id.availability, View.VISIBLE);
-            } else {
-                views.setViewVisibility(R.id.availability, View.GONE);
-            }
-            boolean hasNewStory =
-                    tile.getStatuses() != null && tile.getStatuses().stream().anyMatch(
-                            c -> c.getActivity() == ACTIVITY_NEW_STORY);
-            views.setTextViewText(R.id.name, tile.getUserName().toString());
-            views.setBoolean(R.id.content_background, "setClipToOutline", true);
-            Icon icon = tile.getUserIcon();
-            PeopleStoryIconFactory storyIcon = new PeopleStoryIconFactory(context,
-                    context.getPackageManager(),
-                    IconDrawableFactory.newInstance(context, false),
-                    maxAvatarSize);
-            Drawable drawable = icon.loadDrawable(context);
-            Drawable personDrawable = storyIcon.getPeopleTileDrawable(drawable,
-                    tile.getPackageName(), getUserId(tile), tile.isImportantConversation(),
-                    hasNewStory);
-            Bitmap bitmap = convertDrawableToBitmap(personDrawable);
-            views.setImageViewBitmap(R.id.person_icon, bitmap);
-
-            return views;
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to set common fields: " + e);
-        }
-        return views;
-    }
-
-    private static RemoteViews setLaunchIntents(Context context, RemoteViews views,
-            PeopleSpaceTile tile, int appWidgetId) {
-        try {
-            Intent activityIntent = new Intent(context, LaunchConversationActivity.class);
-            activityIntent.addFlags(
-                    Intent.FLAG_ACTIVITY_NEW_TASK
-                            | Intent.FLAG_ACTIVITY_CLEAR_TASK
-                            | Intent.FLAG_ACTIVITY_NO_HISTORY
-                            | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-            activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, tile.getId());
-            activityIntent.putExtra(
-                    PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, tile.getPackageName());
-            activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE,
-                    tile.getUserHandle());
-            activityIntent.putExtra(
-                    PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY, tile.getNotificationKey());
-            views.setOnClickPendingIntent(R.id.item, PendingIntent.getActivity(
-                    context,
-                    appWidgetId,
-                    activityIntent,
-                    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE));
-            return views;
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to add launch intents: " + e);
-        }
-
-        return views;
-    }
-
-    private static RemoteViews createMissedCallRemoteViews(Context context,
-            PeopleSpaceTile tile, int layoutSize) {
-        int layout = layoutSize == SMALL_LAYOUT ? R.layout.people_space_small_view
-                : R.layout.people_space_small_avatar_tile;
-        RemoteViews views = new RemoteViews(context.getPackageName(), layout);
-        views.setViewVisibility(R.id.subtext, View.GONE);
-        views.setViewVisibility(R.id.text_content, View.VISIBLE);
-        views.setTextViewText(R.id.text_content, tile.getNotificationContent());
-        views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_phone_missed);
-        ensurePredefinedIconVisibleOnSmallView(views, layoutSize);
-        views.setBoolean(R.id.content_background, "setClipToOutline", true);
-        return views;
-    }
-
-    private static void ensurePredefinedIconVisibleOnSmallView(RemoteViews views, int layoutSize) {
-        if (layoutSize == SMALL_LAYOUT) {
-            views.setViewVisibility(R.id.name, View.GONE);
-            views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
-        }
-    }
-
-    private static RemoteViews createNotificationRemoteViews(Context context,
-            PeopleSpaceTile tile, int layoutSize) {
-        int layout = layoutSize == SMALL_LAYOUT ? R.layout.people_space_small_view
-                : R.layout.people_space_small_avatar_tile;
-        RemoteViews views = new RemoteViews(context.getPackageName(), layout);
-        if (layoutSize != MEDIUM_LAYOUT) {
-            views.setViewVisibility(R.id.name, View.GONE);
-            views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
-        }
-        Uri image = tile.getNotificationDataUri();
-        ensurePredefinedIconVisibleOnSmallView(views, layoutSize);
-        if (image != null) {
-            // TODO: Use NotificationInlineImageCache
-            views.setImageViewUri(R.id.image, image);
-            views.setViewVisibility(R.id.content_background, View.VISIBLE);
-            views.setBoolean(R.id.content_background, "setClipToOutline", true);
-            views.setViewVisibility(R.id.text_content, View.GONE);
-            views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_photo_camera);
-        } else {
-            CharSequence content = tile.getNotificationContent();
-            views = setPunctuationRemoteViewsFields(views, content);
-            views.setTextViewText(R.id.text_content, tile.getNotificationContent());
-            // TODO: Measure max lines from height.
-            views.setInt(R.id.text_content, "setMaxLines", 2);
-            TypedValue typedValue = new TypedValue();
-            context.getTheme().resolveAttribute(android.R.attr.textColorPrimary, typedValue, true);
-            int primaryTextColor = context.getColor(typedValue.resourceId);
-            views.setInt(R.id.text_content, "setTextColor", primaryTextColor);
-            views.setViewVisibility(R.id.text_content, View.VISIBLE);
-            views.setViewVisibility(R.id.content_background, View.GONE);
-            views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_message);
-        }
-        // TODO: Set subtext as Group Sender name once storing the name in PeopleSpaceTile.
-        views.setViewVisibility(R.id.subtext, View.GONE);
-        return views;
-    }
-
-    private static RemoteViews createLastInteractionRemoteViews(Context context,
-            PeopleSpaceTile tile, int layoutSize) {
-        int layout = layoutSize == SMALL_LAYOUT ? R.layout.people_space_small_view
-                : R.layout.people_space_large_avatar_tile;
-        RemoteViews views = new RemoteViews(context.getPackageName(), layout);
-        if (layoutSize == SMALL_LAYOUT) {
-            views.setViewVisibility(R.id.name, View.VISIBLE);
-            views.setViewVisibility(R.id.predefined_icon, View.GONE);
-        }
-        String status = PeopleSpaceUtils.getLastInteractionString(
-                context, tile.getLastInteractionTimestamp());
-        views.setTextViewText(R.id.last_interaction, status);
-        return views;
-    }
-
-    private static RemoteViews setPunctuationRemoteViewsFields(
-            RemoteViews views, CharSequence content) {
-        String punctuation = getBackgroundTextFromMessage(content.toString());
-        int visibility = View.GONE;
-        if (punctuation != null) {
-            visibility = View.VISIBLE;
-        }
-        views.setTextViewText(R.id.punctuation1, punctuation);
-        views.setTextViewText(R.id.punctuation2, punctuation);
-        views.setTextViewText(R.id.punctuation3, punctuation);
-        views.setTextViewText(R.id.punctuation4, punctuation);
-        views.setTextViewText(R.id.punctuation5, punctuation);
-        views.setTextViewText(R.id.punctuation6, punctuation);
-
-        views.setViewVisibility(R.id.punctuation1, visibility);
-        views.setViewVisibility(R.id.punctuation2, visibility);
-        views.setViewVisibility(R.id.punctuation3, visibility);
-        views.setViewVisibility(R.id.punctuation4, visibility);
-        views.setViewVisibility(R.id.punctuation5, visibility);
-        views.setViewVisibility(R.id.punctuation6, visibility);
-
-        return views;
-    }
-
-    /** Gets character for tile background decoration based on notification content. */
-    @VisibleForTesting
-    static String getBackgroundTextFromMessage(String message) {
-        if (!ANY_DOUBLE_MARK_PATTERN.matcher(message).find()) {
-            return null;
-        }
-        if (MIXED_MARK_PATTERN.matcher(message).find()) {
-            return "!?";
-        }
-        Matcher doubleQuestionMatcher = DOUBLE_QUESTION_PATTERN.matcher(message);
-        if (!doubleQuestionMatcher.find()) {
-            return "!";
-        }
-        Matcher doubleExclamationMatcher = DOUBLE_EXCLAMATION_PATTERN.matcher(message);
-        if (!doubleExclamationMatcher.find()) {
-            return "?";
-        }
-        // If we have both "!!" and "??", return the one that comes first.
-        if (doubleQuestionMatcher.start() < doubleExclamationMatcher.start()) {
-            return "?";
-        }
-        return "!";
-    }
-
     /** Gets the most recent {@link Notification.MessagingStyle.Message} from the notification. */
     @VisibleForTesting
     public static Notification.MessagingStyle.Message getLastMessagingStyleMessage(
@@ -917,7 +521,8 @@
     public static void updateAppWidgetViews(AppWidgetManager appWidgetManager,
             Context context, int appWidgetId, PeopleSpaceTile tile, Bundle options) {
         if (DEBUG) Log.d(TAG, "Widget: " + appWidgetId + ", " + tile.getUserName());
-        RemoteViews views = createRemoteViews(context, tile, appWidgetId, options);
+        RemoteViews views = new PeopleTileViewHelper(context, tile, appWidgetId,
+                options).getViews();
 
         // Tell the AppWidgetManager to perform an update on the current app widget.
         appWidgetManager.updateAppWidget(appWidgetId, views);
@@ -1005,7 +610,7 @@
 
         if (DEBUG) Log.i(TAG, "Returning tile preview for shortcutId: " + shortcutId);
         Bundle bundle = new Bundle();
-        return PeopleSpaceUtils.createRemoteViews(context, augmentedTile, 0, bundle);
+        return new PeopleTileViewHelper(context, augmentedTile, 0, bundle).getViews();
     }
 
     /** Returns the userId associated with a {@link PeopleSpaceTile} */
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
new file mode 100644
index 0000000..ae81ab04
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -0,0 +1,553 @@
+/*
+ * 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.people;
+
+import static android.app.Notification.CATEGORY_MISSED_CALL;
+import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY;
+import static android.app.people.ConversationStatus.ACTIVITY_AUDIO;
+import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
+import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+import static android.app.people.ConversationStatus.ACTIVITY_LOCATION;
+import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
+import static android.app.people.ConversationStatus.ACTIVITY_UPCOMING_BIRTHDAY;
+import static android.app.people.ConversationStatus.ACTIVITY_VIDEO;
+import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
+import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT;
+import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH;
+import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT;
+import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH;
+
+import static com.android.systemui.people.PeopleSpaceUtils.convertDrawableToBitmap;
+import static com.android.systemui.people.PeopleSpaceUtils.getUserId;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.app.people.ConversationStatus;
+import android.app.people.PeopleSpaceTile;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.IconDrawableFactory;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+import com.android.systemui.people.widget.LaunchConversationActivity;
+import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+class PeopleTileViewHelper {
+    /** Turns on debugging information about People Space. */
+    public static final boolean DEBUG = true;
+    private static final String TAG = "PeopleTileView";
+
+    public static final int LAYOUT_SMALL = 0;
+    public static final int LAYOUT_MEDIUM = 1;
+    public static final int LAYOUT_LARGE = 2;
+
+    private static final int MIN_CONTENT_MAX_LINES = 2;
+
+    private static final int FIXED_HEIGHT_DIMENS_FOR_LARGE_CONTENT = 14 + 12 + 4 + 16;
+    private static final int FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT = 8 + 4 + 4 + 8;
+    private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL = 6 + 4 + 8;
+    private static final int FIXED_WIDTH_DIMENS_FOR_SMALL = 4 + 4;
+
+    private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+");
+    private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+");
+    private static final Pattern ANY_DOUBLE_MARK_PATTERN = Pattern.compile("[!?][!?]+");
+    private static final Pattern MIXED_MARK_PATTERN = Pattern.compile("![?].*|.*[?]!");
+
+    public static final String EMPTY_STRING = "";
+
+    private Context mContext;
+    private PeopleSpaceTile mTile;
+    private float mDensity;
+    private int mAppWidgetId;
+    private int mWidth;
+    private int mHeight;
+    private int mLayoutSize;
+
+    PeopleTileViewHelper(Context context, PeopleSpaceTile tile,
+            int appWidgetId, Bundle options) {
+        mContext = context;
+        mTile = tile;
+        mAppWidgetId = appWidgetId;
+        mDensity = mContext.getResources().getDisplayMetrics().density;
+        int display = mContext.getResources().getConfiguration().orientation;
+        mWidth = display == Configuration.ORIENTATION_PORTRAIT
+                ? options.getInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.default_width)) : options.getInt(
+                OPTION_APPWIDGET_MAX_WIDTH,
+                getSizeInDp(R.dimen.default_width));
+        mHeight = display == Configuration.ORIENTATION_PORTRAIT ? options.getInt(
+                OPTION_APPWIDGET_MAX_HEIGHT,
+                getSizeInDp(R.dimen.default_height))
+                : options.getInt(OPTION_APPWIDGET_MIN_HEIGHT,
+                        getSizeInDp(R.dimen.default_height));
+        mLayoutSize = getLayoutSize();
+    }
+
+    public RemoteViews getViews() {
+        RemoteViews viewsForTile = getViewForTile();
+        int maxAvatarSize = getMaxAvatarSize(viewsForTile);
+        RemoteViews views = setCommonRemoteViewsFields(viewsForTile, maxAvatarSize);
+        return setLaunchIntents(views);
+    }
+
+    /**
+     * The prioritization for the {@code mTile} content is missed calls, followed by notification
+     * content, then birthdays, then the most recent status, and finally last interaction.
+     */
+    private RemoteViews getViewForTile() {
+        if (Objects.equals(mTile.getNotificationCategory(), CATEGORY_MISSED_CALL)) {
+            if (DEBUG) Log.d(TAG, "Create missed call view");
+            return createMissedCallRemoteViews();
+        }
+
+        if (mTile.getNotificationKey() != null) {
+            if (DEBUG) Log.d(TAG, "Create notification view");
+            return createNotificationRemoteViews();
+        }
+
+        // TODO: Add sorting when we expose timestamp of statuses.
+        List<ConversationStatus> statusesForEntireView =
+                mTile.getStatuses() == null ? Arrays.asList() : mTile.getStatuses().stream().filter(
+                        c -> isStatusValidForEntireStatusView(c)).collect(Collectors.toList());
+        ConversationStatus birthdayStatus = getBirthdayStatus(statusesForEntireView);
+        if (birthdayStatus != null) {
+            if (DEBUG) Log.d(TAG, "Create birthday view");
+            return createStatusRemoteViews(birthdayStatus);
+        }
+
+        if (!statusesForEntireView.isEmpty()) {
+            if (DEBUG) {
+                Log.d(TAG,
+                        "Create status view for: " + statusesForEntireView.get(0).getActivity());
+            }
+            return createStatusRemoteViews(statusesForEntireView.get(0));
+        }
+
+        return createLastInteractionRemoteViews();
+    }
+
+    private void setMaxLines(RemoteViews views) {
+        int textSize = mLayoutSize == LAYOUT_LARGE ? getSizeInDp(
+                R.dimen.content_text_size_for_medium)
+                : getSizeInDp(R.dimen.content_text_size_for_medium);
+        int lineHeight = getLineHeight(textSize);
+        int notificationContentHeight = getContentHeightForLayout(lineHeight);
+        int maxAdaptiveLines = Math.floorDiv(notificationContentHeight, lineHeight);
+        int maxLines = Math.max(MIN_CONTENT_MAX_LINES, maxAdaptiveLines);
+        views.setInt(R.id.text_content, "setMaxLines", maxLines);
+    }
+
+    private int getLineHeight(int textSize) {
+        try {
+            TextView text = new TextView(mContext);
+            text.setTextSize(textSize);
+            int lineHeight = (int) (text.getLineHeight() / mDensity);
+            return lineHeight;
+        } catch (Exception e) {
+            Log.e(TAG, "Could not create text view: " + e);
+            return getSizeInDp(
+                    R.dimen.content_text_size_for_medium);
+        }
+    }
+
+    private int getSizeInDp(int dimenResourceId) {
+        return (int) (mContext.getResources().getDimension(dimenResourceId) / mDensity);
+    }
+
+    private int getContentHeightForLayout(int lineHeight) {
+        switch (mLayoutSize) {
+            case LAYOUT_MEDIUM:
+                return mHeight - (lineHeight + FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT);
+            case LAYOUT_LARGE:
+                return mHeight - (getSizeInDp(
+                        R.dimen.max_people_avatar_size_for_large_content) + lineHeight
+                        + FIXED_HEIGHT_DIMENS_FOR_LARGE_CONTENT);
+            default:
+                return -1;
+        }
+    }
+
+    /** Calculates the best layout relative to the size in {@code options}. */
+    private int getLayoutSize() {
+        if (mHeight >= getSizeInDp(R.dimen.required_width_for_large)
+                && mWidth >= getSizeInDp(R.dimen.required_width_for_large)) {
+            if (DEBUG) Log.d(TAG, "Large view for mWidth: " + mWidth + " mHeight: " + mHeight);
+            return LAYOUT_LARGE;
+        }
+        // Small layout used below a certain minimum mWidth with any mHeight.
+        if (mWidth >= getSizeInDp(R.dimen.required_width_for_medium)) {
+            if (DEBUG) Log.d(TAG, "Medium view for mWidth: " + mWidth + " mHeight: " + mHeight);
+            return LAYOUT_MEDIUM;
+        }
+        // Small layout can always handle our minimum mWidth and mHeight for our widget.
+        if (DEBUG) Log.d(TAG, "Small view for mWidth: " + mWidth + " mHeight: " + mHeight);
+        return LAYOUT_SMALL;
+    }
+
+    /** Returns the max avatar size for {@code views} under the current {@code options}. */
+    private int getMaxAvatarSize(RemoteViews views) {
+        int layoutId = views.getLayoutId();
+        int avatarSize = getSizeInDp(R.dimen.avatar_size_for_medium);
+        if (layoutId == R.layout.people_tile_medium_empty) {
+            return getSizeInDp(
+                    R.dimen.max_people_avatar_size_for_large_content);
+        }
+        if (layoutId == R.layout.people_tile_medium_with_content) {
+            return getSizeInDp(R.dimen.avatar_size_for_medium);
+        }
+
+        // Calculate adaptive avatar size for remaining layouts.
+        if (layoutId == R.layout.people_tile_small) {
+            int avatarHeightSpace = mHeight - (FIXED_HEIGHT_DIMENS_FOR_SMALL + Math.max(18,
+                    getLineHeight(getSizeInDp(
+                            R.dimen.name_text_size_for_small))));
+            int avatarWidthSpace = mWidth - FIXED_WIDTH_DIMENS_FOR_SMALL;
+            avatarSize = Math.min(avatarHeightSpace, avatarWidthSpace);
+        }
+
+        if (layoutId == R.layout.people_tile_large_with_content) {
+            avatarSize = mHeight - (FIXED_HEIGHT_DIMENS_FOR_LARGE_CONTENT + (getLineHeight(
+                    getSizeInDp(R.dimen.content_text_size_for_large))
+                    * 3));
+            return Math.min(avatarSize, getSizeInDp(
+                    R.dimen.max_people_avatar_size_for_large_content));
+        }
+
+        if (layoutId == R.layout.people_tile_large_empty) {
+            int avatarHeightSpace = mHeight - (14 + 14 + getLineHeight(
+                    getSizeInDp(R.dimen.name_text_size_for_large))
+                    + getLineHeight(
+                    getSizeInDp(R.dimen.content_text_size_for_large))
+                    + 16 + 10 + 14);
+            int avatarWidthSpace = mWidth - (14 + 14);
+            avatarSize = Math.min(avatarHeightSpace, avatarWidthSpace);
+        }
+        return Math.min(avatarSize,
+                getSizeInDp(R.dimen.max_people_avatar_size));
+    }
+
+    private RemoteViews setCommonRemoteViewsFields(RemoteViews views,
+            int maxAvatarSize) {
+        try {
+            boolean isAvailable =
+                    mTile.getStatuses() != null && mTile.getStatuses().stream().anyMatch(
+                            c -> c.getAvailability() == AVAILABILITY_AVAILABLE);
+            if (isAvailable) {
+                views.setViewVisibility(R.id.availability, View.VISIBLE);
+            } else {
+                views.setViewVisibility(R.id.availability, View.GONE);
+            }
+            boolean hasNewStory =
+                    mTile.getStatuses() != null && mTile.getStatuses().stream().anyMatch(
+                            c -> c.getActivity() == ACTIVITY_NEW_STORY);
+            views.setTextViewText(R.id.name, mTile.getUserName().toString());
+            views.setBoolean(R.id.image, "setClipToOutline", true);
+
+            Icon icon = mTile.getUserIcon();
+            PeopleStoryIconFactory storyIcon = new PeopleStoryIconFactory(mContext,
+                    mContext.getPackageManager(),
+                    IconDrawableFactory.newInstance(mContext, false),
+                    maxAvatarSize);
+            Drawable drawable = icon.loadDrawable(mContext);
+            Drawable personDrawable = storyIcon.getPeopleTileDrawable(drawable,
+                    mTile.getPackageName(), getUserId(mTile), mTile.isImportantConversation(),
+                    hasNewStory);
+            Bitmap bitmap = convertDrawableToBitmap(personDrawable);
+            views.setImageViewBitmap(R.id.person_icon, bitmap);
+
+            return views;
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to set common fields: " + e);
+        }
+        return views;
+    }
+
+    private RemoteViews setLaunchIntents(RemoteViews views) {
+        try {
+            Intent activityIntent = new Intent(mContext, LaunchConversationActivity.class);
+            activityIntent.addFlags(
+                    Intent.FLAG_ACTIVITY_NEW_TASK
+                            | Intent.FLAG_ACTIVITY_CLEAR_TASK
+                            | Intent.FLAG_ACTIVITY_NO_HISTORY
+                            | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+            activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, mTile.getId());
+            activityIntent.putExtra(
+                    PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, mTile.getPackageName());
+            activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE,
+                    mTile.getUserHandle());
+            activityIntent.putExtra(
+                    PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY, mTile.getNotificationKey());
+            views.setOnClickPendingIntent(R.id.item, PendingIntent.getActivity(
+                    mContext,
+                    mAppWidgetId,
+                    activityIntent,
+                    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE));
+            return views;
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to add launch intents: " + e);
+        }
+
+        return views;
+    }
+
+    private RemoteViews createMissedCallRemoteViews() {
+        RemoteViews views = getViewForContentLayout();
+        views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
+        setMaxLines(views);
+        views.setTextViewText(R.id.text_content, mTile.getNotificationContent());
+        views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_phone_missed);
+        return views;
+    }
+
+    private RemoteViews createNotificationRemoteViews() {
+        RemoteViews views = getViewForContentLayout();
+        Uri image = mTile.getNotificationDataUri();
+        if (image != null) {
+            // TODO: Use NotificationInlineImageCache
+            views.setImageViewUri(R.id.image, image);
+            views.setViewVisibility(R.id.image, View.VISIBLE);
+            views.setViewVisibility(R.id.text_content, View.GONE);
+            views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_photo_camera);
+        } else {
+            setMaxLines(views);
+            CharSequence content = mTile.getNotificationContent();
+            views = setPunctuationRemoteViewsFields(views, content);
+            TypedValue typedValue = new TypedValue();
+            mContext.getTheme().resolveAttribute(android.R.attr.textColorPrimary, typedValue, true);
+            int primaryTextColor = mContext.getColor(typedValue.resourceId);
+            views.setInt(R.id.text_content, "setTextColor", primaryTextColor);
+            views.setTextViewText(R.id.text_content, mTile.getNotificationContent());
+            views.setViewVisibility(R.id.image, View.GONE);
+            views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_message);
+        }
+        // TODO: Set subtext as Group Sender name once storing the name in PeopleSpaceTile and
+        //  subtract 1 from maxLines when present.
+        views.setViewVisibility(R.id.subtext, View.GONE);
+        return views;
+    }
+
+    private RemoteViews createStatusRemoteViews(ConversationStatus status) {
+        RemoteViews views = getViewForContentLayout();
+        CharSequence statusText = status.getDescription();
+        if (TextUtils.isEmpty(statusText)) {
+            statusText = getStatusTextByType(status.getActivity());
+        }
+        views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
+        setMaxLines(views);
+        // Secondary text color for statuses.
+        TypedValue typedValue = new TypedValue();
+        mContext.getTheme().resolveAttribute(android.R.attr.textColorSecondary, typedValue, true);
+        int secondaryTextColor = mContext.getColor(typedValue.resourceId);
+        views.setInt(R.id.text_content, "setTextColor", secondaryTextColor);
+        views.setTextViewText(R.id.text_content, statusText);
+
+        Icon statusIcon = status.getIcon();
+        if (statusIcon != null) {
+            // No multi-line text with status images on medium layout.
+            views.setViewVisibility(R.id.text_content, View.GONE);
+            // Show 1-line subtext on large layout with status images.
+            if (mLayoutSize == LAYOUT_LARGE) {
+                views.setViewVisibility(R.id.subtext, View.VISIBLE);
+                views.setTextViewText(R.id.subtext, statusText);
+            }
+            views.setViewVisibility(R.id.image, View.VISIBLE);
+            views.setImageViewIcon(R.id.image, statusIcon);
+        } else {
+            views.setViewVisibility(R.id.image, View.GONE);
+        }
+        // TODO: Set status pre-defined icons
+        views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_person);
+        return views;
+    }
+
+    @Nullable
+    private ConversationStatus getBirthdayStatus(
+            List<ConversationStatus> statuses) {
+        Optional<ConversationStatus> birthdayStatus = statuses.stream().filter(
+                c -> c.getActivity() == ACTIVITY_BIRTHDAY).findFirst();
+        if (birthdayStatus.isPresent()) {
+            return birthdayStatus.get();
+        }
+        if (!TextUtils.isEmpty(mTile.getBirthdayText())) {
+            return new ConversationStatus.Builder(mTile.getId(), ACTIVITY_BIRTHDAY).build();
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns whether a {@code status} should have its own entire templated view.
+     *
+     * <p>A status may still be shown on the view (for example, as a new story ring) even if it's
+     * not valid to compose an entire view.
+     */
+    private boolean isStatusValidForEntireStatusView(ConversationStatus status) {
+        switch (status.getActivity()) {
+            // Birthday & Anniversary don't require text provided or icon provided.
+            case ACTIVITY_BIRTHDAY:
+            case ACTIVITY_ANNIVERSARY:
+                return true;
+            default:
+                // For future birthday, location, new story, video, music, game, and other, the
+                // app must provide either text or an icon.
+                return !TextUtils.isEmpty(status.getDescription())
+                        || status.getIcon() != null;
+        }
+    }
+
+    private String getStatusTextByType(int activity) {
+        switch (activity) {
+            case ACTIVITY_BIRTHDAY:
+                return mContext.getString(R.string.birthday_status);
+            case ACTIVITY_UPCOMING_BIRTHDAY:
+                return mContext.getString(R.string.upcoming_birthday_status);
+            case ACTIVITY_ANNIVERSARY:
+                return mContext.getString(R.string.anniversary_status);
+            case ACTIVITY_LOCATION:
+                return mContext.getString(R.string.location_status);
+            case ACTIVITY_NEW_STORY:
+                return mContext.getString(R.string.new_story_status);
+            case ACTIVITY_VIDEO:
+                return mContext.getString(R.string.video_status);
+            case ACTIVITY_AUDIO:
+                return mContext.getString(R.string.audio_status);
+            case ACTIVITY_GAME:
+                return mContext.getString(R.string.game_status);
+            default:
+                return EMPTY_STRING;
+        }
+    }
+
+    private RemoteViews setPunctuationRemoteViewsFields(
+            RemoteViews views, CharSequence content) {
+        String punctuation = getBackgroundTextFromMessage(content.toString());
+        int visibility = View.GONE;
+        if (punctuation != null) {
+            visibility = View.VISIBLE;
+        }
+        views.setTextViewText(R.id.punctuation1, punctuation);
+        views.setTextViewText(R.id.punctuation2, punctuation);
+        views.setTextViewText(R.id.punctuation3, punctuation);
+        views.setTextViewText(R.id.punctuation4, punctuation);
+        views.setTextViewText(R.id.punctuation5, punctuation);
+        views.setTextViewText(R.id.punctuation6, punctuation);
+
+        views.setViewVisibility(R.id.punctuation1, visibility);
+        views.setViewVisibility(R.id.punctuation2, visibility);
+        views.setViewVisibility(R.id.punctuation3, visibility);
+        views.setViewVisibility(R.id.punctuation4, visibility);
+        views.setViewVisibility(R.id.punctuation5, visibility);
+        views.setViewVisibility(R.id.punctuation6, visibility);
+
+        return views;
+    }
+
+    /** Gets character for mTile background decoration based on notification content. */
+    @VisibleForTesting
+    String getBackgroundTextFromMessage(String message) {
+        if (!ANY_DOUBLE_MARK_PATTERN.matcher(message).find()) {
+            return null;
+        }
+        if (MIXED_MARK_PATTERN.matcher(message).find()) {
+            return "!?";
+        }
+        Matcher doubleQuestionMatcher = DOUBLE_QUESTION_PATTERN.matcher(message);
+        if (!doubleQuestionMatcher.find()) {
+            return "!";
+        }
+        Matcher doubleExclamationMatcher = DOUBLE_EXCLAMATION_PATTERN.matcher(message);
+        if (!doubleExclamationMatcher.find()) {
+            return "?";
+        }
+        // If we have both "!!" and "??", return the one that comes first.
+        if (doubleQuestionMatcher.start() < doubleExclamationMatcher.start()) {
+            return "?";
+        }
+        return "!";
+    }
+
+    private RemoteViews getViewForContentLayout() {
+        RemoteViews views = new RemoteViews(mContext.getPackageName(),
+                getLayoutForContent());
+        if (mLayoutSize == LAYOUT_SMALL) {
+            views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
+            views.setViewVisibility(R.id.name, View.GONE);
+        } else {
+            views.setViewVisibility(R.id.predefined_icon, View.GONE);
+            views.setViewVisibility(R.id.name, View.VISIBLE);
+            views.setViewVisibility(R.id.text_content, View.VISIBLE);
+            views.setViewVisibility(R.id.subtext, View.GONE);
+        }
+        return views;
+    }
+
+    private RemoteViews createLastInteractionRemoteViews() {
+        RemoteViews views = new RemoteViews(mContext.getPackageName(), getEmptyLayout());
+        if (mLayoutSize == LAYOUT_SMALL) {
+            views.setViewVisibility(R.id.name, View.VISIBLE);
+            views.setViewVisibility(R.id.predefined_icon, View.GONE);
+        }
+        String status = PeopleSpaceUtils.getLastInteractionString(mContext,
+                mTile.getLastInteractionTimestamp());
+        views.setTextViewText(R.id.last_interaction, status);
+        return views;
+    }
+
+    private int getEmptyLayout() {
+        switch (mLayoutSize) {
+            case LAYOUT_MEDIUM:
+                return R.layout.people_tile_medium_empty;
+            case LAYOUT_LARGE:
+                return R.layout.people_tile_large_empty;
+            case LAYOUT_SMALL:
+            default:
+                return R.layout.people_tile_small;
+        }
+    }
+
+    private int getLayoutForContent() {
+        switch (mLayoutSize) {
+            case LAYOUT_MEDIUM:
+                return R.layout.people_tile_medium_with_content;
+            case LAYOUT_LARGE:
+                return R.layout.people_tile_large_with_content;
+            case LAYOUT_SMALL:
+            default:
+                return R.layout.people_tile_small;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index eedcdab..b1689f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -48,7 +48,6 @@
 import com.android.systemui.qs.carrier.QSCarrierGroupController;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusIconContainer;
 import com.android.systemui.statusbar.policy.Clock;
@@ -86,7 +85,6 @@
     private final View mRingerContainer;
     private final QSTileHost mQSTileHost;
     private final StatusBarIconController mStatusBarIconController;
-    private final CommandQueue mCommandQueue;
     private final DemoModeController mDemoModeController;
     private final UserTracker mUserTracker;
     private final StatusIconContainer mIconContainer;
@@ -204,7 +202,7 @@
             PrivacyItemController privacyItemController, RingerModeTracker ringerModeTracker,
             ActivityStarter activityStarter, UiEventLogger uiEventLogger,
             QSTileHost qsTileHost, StatusBarIconController statusBarIconController,
-            CommandQueue commandQueue, DemoModeController demoModeController,
+            DemoModeController demoModeController,
             UserTracker userTracker, QuickQSPanelController quickQSPanelController,
             QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder,
             PrivacyLogger privacyLogger,
@@ -219,7 +217,6 @@
         mUiEventLogger = uiEventLogger;
         mQSTileHost = qsTileHost;
         mStatusBarIconController = statusBarIconController;
-        mCommandQueue = commandQueue;
         mDemoModeController = demoModeController;
         mUserTracker = userTracker;
         mLifecycle = new LifecycleRegistry(mLifecycleOwner);
@@ -238,7 +235,7 @@
         mRingerContainer = mView.findViewById(R.id.ringer_container);
         mIconContainer = mView.findViewById(R.id.statusIcons);
 
-        mIconManager = new StatusBarIconController.TintedIconManager(mIconContainer, mCommandQueue);
+        mIconManager = new StatusBarIconController.TintedIconManager(mIconContainer);
         mDemoModeReceiver = new ClockDemoModeReceiver(mClockView);
         mColorExtractor = colorExtractor;
         mOnColorsChangedListener = (extractor, which) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
index 1ec175c..78ee896 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.screenshot;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -25,6 +23,7 @@
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -32,6 +31,7 @@
 import android.util.IntArray;
 import android.util.Log;
 import android.util.MathUtils;
+import android.util.Range;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
@@ -49,29 +49,30 @@
  */
 public class CropView extends View {
     private static final String TAG = "CropView";
+
     public enum CropBoundary {
-        NONE, TOP, BOTTOM
+        NONE, TOP, BOTTOM, LEFT, RIGHT
     }
 
     private final float mCropTouchMargin;
     private final Paint mShadePaint;
     private final Paint mHandlePaint;
 
-    // Top and bottom crops are stored as floats [0, 1], representing the top and bottom of the
-    // view, respectively.
-    private float mTopCrop = 0f;
-    private float mBottomCrop = 1f;
-
-    // When the user is dragging a handle, these variables store the distance between the top/bottom
-    // crop values and
-    private float mTopDelta = 0f;
-    private float mBottomDelta = 0f;
+    // Crop rect with each element represented as [0,1] along its proper axis.
+    private RectF mCrop = new RectF(0, 0, 1, 1);
 
     private int mExtraTopPadding;
     private int mExtraBottomPadding;
+    private int mImageWidth;
 
     private CropBoundary mCurrentDraggingBoundary = CropBoundary.NONE;
+    // The starting value of mCurrentDraggingBoundary's crop, used to compute touch deltas.
+    private float mMovementStartValue;
     private float mStartingY;  // y coordinate of ACTION_DOWN
+    private float mStartingX;
+    // The allowable values for the current boundary being dragged
+    private Range<Float> mMotionRange;
+
     private CropInteractionListener mCropInteractionListener;
 
     public CropView(Context context, @Nullable AttributeSet attrs) {
@@ -86,6 +87,7 @@
         mShadePaint.setColor(t.getColor(R.styleable.CropView_scrimColor, Color.TRANSPARENT));
         mHandlePaint = new Paint();
         mHandlePaint.setColor(t.getColor(R.styleable.CropView_handleColor, Color.BLACK));
+        mHandlePaint.setStrokeCap(Paint.Cap.ROUND);
         mHandlePaint.setStrokeWidth(
                 t.getDimensionPixelSize(R.styleable.CropView_handleThickness, 20));
         t.recycle();
@@ -100,8 +102,7 @@
         Parcelable superState = super.onSaveInstanceState();
 
         SavedState ss = new SavedState(superState);
-        ss.mTopBoundary = getTopBoundary();
-        ss.mBottomBoundary = getBottomBoundary();
+        ss.mCrop = mCrop;
         return ss;
     }
 
@@ -110,45 +111,67 @@
         SavedState ss = (SavedState) state;
         super.onRestoreInstanceState(ss.getSuperState());
 
-        setBoundaryTo(CropBoundary.TOP, ss.mTopBoundary);
-        setBoundaryTo(CropBoundary.BOTTOM, ss.mBottomBoundary);
+        mCrop = ss.mCrop;
     }
 
     @Override
     public void onDraw(Canvas canvas) {
         super.onDraw(canvas);
-        float top = mTopCrop + mTopDelta;
-        float bottom = mBottomCrop + mBottomDelta;
-        drawShade(canvas, 0, top);
-        drawShade(canvas, bottom, 1f);
-        drawHandle(canvas, top, /* draw the handle tab down */ false);
-        drawHandle(canvas, bottom, /* draw the handle tab up */ true);
+        drawShade(canvas, 0, 0, 1, mCrop.top);
+        drawShade(canvas, 0, mCrop.bottom, 1, 1);
+        drawShade(canvas, 0, mCrop.top, mCrop.left, mCrop.bottom);
+        drawShade(canvas, mCrop.right, mCrop.top, 1, mCrop.bottom);
+        drawHorizontalHandle(canvas, mCrop.top, /* draw the handle tab up */ true);
+        drawHorizontalHandle(canvas, mCrop.bottom, /* draw the handle tab down */ false);
+        drawVerticalHandle(canvas, mCrop.left, /* left */ true);
+        drawVerticalHandle(canvas, mCrop.right, /* right */ false);
     }
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        int topPx = fractionToPixels(mTopCrop);
-        int bottomPx = fractionToPixels(mBottomCrop);
+        int topPx = fractionToVerticalPixels(mCrop.top);
+        int bottomPx = fractionToVerticalPixels(mCrop.bottom);
         switch (event.getAction()) {
             case MotionEvent.ACTION_DOWN:
-                mCurrentDraggingBoundary = nearestBoundary(event, topPx, bottomPx);
+                mCurrentDraggingBoundary = nearestBoundary(event, topPx, bottomPx,
+                        fractionToHorizontalPixels(mCrop.left),
+                        fractionToHorizontalPixels(mCrop.right));
                 if (mCurrentDraggingBoundary != CropBoundary.NONE) {
                     mStartingY = event.getY();
+                    mStartingX = event.getX();
+                    mMovementStartValue = getBoundaryPosition(mCurrentDraggingBoundary);
                     updateListener(event);
+                    switch (mCurrentDraggingBoundary) {
+                        case TOP:
+                            mMotionRange = new Range<>(0f,
+                                    mCrop.bottom - pixelDistanceToFraction(mCropTouchMargin,
+                                            CropBoundary.BOTTOM));
+                            break;
+                        case BOTTOM:
+                            mMotionRange = new Range<>(
+                                    mCrop.top + pixelDistanceToFraction(mCropTouchMargin,
+                                            CropBoundary.TOP), 1f);
+                            break;
+                        case LEFT:
+                            mMotionRange = new Range<>(0f,
+                                    mCrop.right - pixelDistanceToFraction(mCropTouchMargin,
+                                            CropBoundary.RIGHT));
+                            break;
+                        case RIGHT:
+                            mMotionRange = new Range<>(
+                                    mCrop.left + pixelDistanceToFraction(mCropTouchMargin,
+                                            CropBoundary.LEFT), 1f);
+                            break;
+                    }
                 }
                 return true;
             case MotionEvent.ACTION_MOVE:
                 if (mCurrentDraggingBoundary != CropBoundary.NONE) {
-                    float delta = event.getY() - mStartingY;
-                    if (mCurrentDraggingBoundary == CropBoundary.TOP) {
-                        mTopDelta = pixelDistanceToFraction((int) MathUtils.constrain(delta,
-                                -topPx + mExtraTopPadding,
-                                bottomPx - 2 * mCropTouchMargin - topPx));
-                    } else {  // Bottom
-                        mBottomDelta = pixelDistanceToFraction((int) MathUtils.constrain(delta,
-                                topPx + 2 * mCropTouchMargin - bottomPx,
-                                getHeight() - bottomPx - mExtraBottomPadding));
-                    }
+                    float deltaPx = isVertical(mCurrentDraggingBoundary) ? event.getY() - mStartingY
+                            : event.getX() - mStartingX;
+                    float delta = pixelDistanceToFraction((int) deltaPx, mCurrentDraggingBoundary);
+                    setBoundaryPosition(mCurrentDraggingBoundary,
+                            mMotionRange.clamp(mMovementStartValue + delta));
                     updateListener(event);
                     invalidate();
                     return true;
@@ -156,8 +179,6 @@
             case MotionEvent.ACTION_CANCEL:
             case MotionEvent.ACTION_UP:
                 if (mCurrentDraggingBoundary != CropBoundary.NONE) {
-                    // Commit the delta to the stored crop values.
-                    commitDeltas(mCurrentDraggingBoundary);
                     updateListener(event);
                 }
         }
@@ -167,22 +188,46 @@
     /**
      * Set the given boundary to the given value without animation.
      */
-    public void setBoundaryTo(CropBoundary boundary, float value) {
+    public void setBoundaryPosition(CropBoundary boundary, float position) {
         switch (boundary) {
             case TOP:
-                mTopCrop = value;
+                mCrop.top = position;
                 break;
             case BOTTOM:
-                mBottomCrop = value;
+                mCrop.bottom = position;
+                break;
+            case LEFT:
+                mCrop.left = position;
+                break;
+            case RIGHT:
+                mCrop.right = position;
                 break;
             case NONE:
-                Log.w(TAG, "No boundary selected for animation");
+                Log.w(TAG, "No boundary selected");
                 break;
         }
 
         invalidate();
     }
 
+    private float getBoundaryPosition(CropBoundary boundary) {
+        switch (boundary) {
+            case TOP:
+                return mCrop.top;
+            case BOTTOM:
+                return mCrop.bottom;
+            case LEFT:
+                return mCrop.left;
+            case RIGHT:
+                return mCrop.right;
+        }
+        return 0;
+    }
+
+    private static boolean isVertical(CropBoundary boundary) {
+        return boundary == CropBoundary.TOP || boundary == CropBoundary.BOTTOM;
+    }
+
     /**
      * Animate the given boundary to the given value.
      */
@@ -191,28 +236,13 @@
             Log.w(TAG, "No boundary selected for animation");
             return;
         }
-        float totalDelta = (boundary == CropBoundary.TOP) ? (value - mTopCrop)
-                : (value - mBottomCrop);
+        float start = getBoundaryPosition(boundary);
         ValueAnimator animator = new ValueAnimator();
         animator.addUpdateListener(animation -> {
-            if (boundary == CropBoundary.TOP) {
-                mTopDelta = animation.getAnimatedFraction() * totalDelta;
-            } else {
-                mBottomDelta = animation.getAnimatedFraction() * totalDelta;
-            }
+            setBoundaryPosition(boundary,
+                    MathUtils.lerp(start, value, animation.getAnimatedFraction()));
             invalidate();
         });
-        animator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                commitDeltas(boundary);
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                commitDeltas(boundary);
-            }
-        });
         animator.setFloatValues(0f, 1f);
         animator.setDuration(750);
         animator.setInterpolator(new FastOutSlowInInterpolator());
@@ -230,65 +260,79 @@
     }
 
     /**
-     * @return value [0,1] representing the position of the top crop boundary. Does not reflect
-     * changes from any in-progress touch input.
+     * Set the pixel width of the image on the screen (on-screen dimension, not actual bitmap
+     * dimension)
      */
-    public float getTopBoundary() {
-        return mTopCrop;
+    public void setImageWidth(int width) {
+        mImageWidth = width;
+        invalidate();
     }
 
     /**
-     * @return value [0,1] representing the position of the bottom crop boundary. Does not reflect
-     * changes from any in-progress touch input.
+     * @return RectF with values [0,1] representing the position of the boundaries along image axes.
      */
-    public float getBottomBoundary() {
-        return mBottomCrop;
+    public Rect getCropBoundaries(int imageWidth, int imageHeight) {
+        return new Rect((int) (mCrop.left * imageWidth), (int) (mCrop.top * imageHeight),
+                (int) (mCrop.right * imageWidth), (int) (mCrop.bottom * imageHeight));
     }
 
     public void setCropInteractionListener(CropInteractionListener listener) {
         mCropInteractionListener = listener;
     }
 
-    private void commitDeltas(CropBoundary boundary) {
-        if (boundary == CropBoundary.TOP) {
-            mTopCrop += mTopDelta;
-            mTopDelta = 0;
-        } else if (boundary == CropBoundary.BOTTOM) {
-            mBottomCrop += mBottomDelta;
-            mBottomDelta = 0;
-        }
-    }
-
     private void updateListener(MotionEvent event) {
-        if (mCropInteractionListener != null) {
-            float boundaryPosition = (mCurrentDraggingBoundary == CropBoundary.TOP)
-                    ? mTopCrop + mTopDelta : mBottomCrop + mBottomDelta;
+        if (mCropInteractionListener != null && (isVertical(mCurrentDraggingBoundary))) {
+            float boundaryPosition = getBoundaryPosition(mCurrentDraggingBoundary);
             mCropInteractionListener.onCropMotionEvent(event, mCurrentDraggingBoundary,
-                    boundaryPosition, fractionToPixels(boundaryPosition));
+                    boundaryPosition, fractionToVerticalPixels(boundaryPosition),
+                    (mCrop.left + mCrop.right) / 2);
         }
     }
 
-    private void drawShade(Canvas canvas, float fracStart, float fracEnd) {
-        canvas.drawRect(0, fractionToPixels(fracStart), getWidth(),
-                fractionToPixels(fracEnd), mShadePaint);
+    /**
+     * Draw a shade to the given canvas with the given [0,1] fractional image bounds.
+     */
+    private void drawShade(Canvas canvas, float left, float top, float right, float bottom) {
+        canvas.drawRect(fractionToHorizontalPixels(left), fractionToVerticalPixels(top),
+                fractionToHorizontalPixels(right),
+                fractionToVerticalPixels(bottom), mShadePaint);
     }
 
-    private void drawHandle(Canvas canvas, float frac, boolean handleTabUp) {
-        int y = fractionToPixels(frac);
-        canvas.drawLine(0, y, getWidth(), y, mHandlePaint);
-        float radius = 15 * getResources().getDisplayMetrics().density;
-        float x = getWidth() * .9f;
+    private void drawHorizontalHandle(Canvas canvas, float frac, boolean handleTabUp) {
+        int y = fractionToVerticalPixels(frac);
+        canvas.drawLine(fractionToHorizontalPixels(mCrop.left), y,
+                fractionToHorizontalPixels(mCrop.right), y, mHandlePaint);
+        float radius = 8 * getResources().getDisplayMetrics().density;
+        int x = (fractionToHorizontalPixels(mCrop.left) + fractionToHorizontalPixels(mCrop.right))
+                / 2;
         canvas.drawArc(x - radius, y - radius, x + radius, y + radius, handleTabUp ? 180 : 0, 180,
                 true, mHandlePaint);
     }
 
+    private void drawVerticalHandle(Canvas canvas, float frac, boolean handleTabLeft) {
+        int x = fractionToHorizontalPixels(frac);
+        canvas.drawLine(x, fractionToVerticalPixels(mCrop.top), x,
+                fractionToVerticalPixels(mCrop.bottom), mHandlePaint);
+        float radius = 8 * getResources().getDisplayMetrics().density;
+        int y = (fractionToVerticalPixels(getBoundaryPosition(CropBoundary.TOP))
+                + fractionToVerticalPixels(
+                getBoundaryPosition(CropBoundary.BOTTOM))) / 2;
+        canvas.drawArc(x - radius, y - radius, x + radius, y + radius, handleTabLeft ? 90 : 270,
+                180,
+                true, mHandlePaint);
+    }
+
     /**
      * Convert the given fraction position to pixel position within the View.
      */
-    private int fractionToPixels(float frac) {
+    private int fractionToVerticalPixels(float frac) {
         return (int) (mExtraTopPadding + frac * getImageHeight());
     }
 
+    private int fractionToHorizontalPixels(float frac) {
+        return (int) ((getWidth() - mImageWidth) / 2 + frac * mImageWidth);
+    }
+
     private int getImageHeight() {
         return getHeight() - mExtraTopPadding - mExtraBottomPadding;
     }
@@ -296,17 +340,30 @@
     /**
      * Convert the given pixel distance to fraction of the image.
      */
-    private float pixelDistanceToFraction(int px) {
-        return px / (float) getImageHeight();
+    private float pixelDistanceToFraction(float px, CropBoundary boundary) {
+        if (isVertical(boundary)) {
+            return px / getImageHeight();
+        } else {
+            return px / mImageWidth;
+        }
     }
 
-    private CropBoundary nearestBoundary(MotionEvent event, int topPx, int bottomPx) {
+    private CropBoundary nearestBoundary(MotionEvent event, int topPx, int bottomPx, int leftPx,
+            int rightPx) {
         if (Math.abs(event.getY() - topPx) < mCropTouchMargin) {
             return CropBoundary.TOP;
         }
         if (Math.abs(event.getY() - bottomPx) < mCropTouchMargin) {
             return CropBoundary.BOTTOM;
         }
+        if (event.getY() > topPx || event.getY() < bottomPx) {
+            if (Math.abs(event.getX() - leftPx) < mCropTouchMargin) {
+                return CropBoundary.LEFT;
+            }
+            if (Math.abs(event.getX() - rightPx) < mCropTouchMargin) {
+                return CropBoundary.RIGHT;
+            }
+        }
         return CropBoundary.NONE;
     }
 
@@ -321,10 +378,10 @@
 
         @Override
         protected int getVirtualViewAt(float x, float y) {
-            if (Math.abs(y - fractionToPixels(mTopCrop)) < mCropTouchMargin) {
+            if (Math.abs(y - fractionToVerticalPixels(mCrop.top)) < mCropTouchMargin) {
                 return TOP_HANDLE_ID;
             }
-            if (Math.abs(y - fractionToPixels(mBottomCrop)) < mCropTouchMargin) {
+            if (Math.abs(y - fractionToVerticalPixels(mCrop.bottom)) < mCropTouchMargin) {
                 return BOTTOM_HANDLE_ID;
             }
             return ExploreByTouchHelper.INVALID_ID;
@@ -338,7 +395,7 @@
 
         @Override
         protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
-            switch(virtualViewId) {
+            switch (virtualViewId) {
                 case TOP_HANDLE_ID:
                     event.setContentDescription(
                             getResources().getString(R.string.screenshot_top_boundary));
@@ -353,16 +410,16 @@
         @Override
         protected void onPopulateNodeForVirtualView(int virtualViewId,
                 AccessibilityNodeInfo node) {
-            switch(virtualViewId) {
+            switch (virtualViewId) {
                 case TOP_HANDLE_ID:
                     node.setContentDescription(
                             getResources().getString(R.string.screenshot_top_boundary));
-                    setNodePositions(mTopCrop, node);
+                    setNodePositions(mCrop.top, node);
                     break;
                 case BOTTOM_HANDLE_ID:
                     node.setContentDescription(
                             getResources().getString(R.string.screenshot_bottom_boundary));
-                    setNodePositions(mBottomCrop, node);
+                    setNodePositions(mCrop.bottom, node);
                     break;
             }
 
@@ -380,7 +437,7 @@
         }
 
         private void setNodePositions(float fraction, AccessibilityNodeInfo node) {
-            int pixels = fractionToPixels(fraction);
+            int pixels = fractionToVerticalPixels(fraction);
             Rect rect = new Rect(0, (int) (pixels - mCropTouchMargin),
                     getWidth(), (int) (pixels + mCropTouchMargin));
             node.setBoundsInParent(rect);
@@ -400,12 +457,11 @@
          * boundaries.
          */
         void onCropMotionEvent(MotionEvent event, CropBoundary boundary, float boundaryPosition,
-                int boundaryPositionPx);
+                int boundaryPositionPx, float horizontalCenter);
     }
 
     static class SavedState extends BaseSavedState {
-        float mTopBoundary;
-        float mBottomBoundary;
+        RectF mCrop;
 
         /**
          * Constructor called from {@link CropView#onSaveInstanceState()}
@@ -419,15 +475,13 @@
          */
         private SavedState(Parcel in) {
             super(in);
-            mTopBoundary = in.readFloat();
-            mBottomBoundary = in.readFloat();
+            mCrop = in.readParcelable(ClassLoader.getSystemClassLoader());
         }
 
         @Override
         public void writeToParcel(Parcel out, int flags) {
             super.writeToParcel(out, flags);
-            out.writeFloat(mTopBoundary);
-            out.writeFloat(mBottomBoundary);
+            out.writeParcelable(mCrop, 0);
         }
 
         public static final Parcelable.Creator<SavedState> CREATOR
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 6a004c2..01afacf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -128,7 +128,7 @@
 
         mPreview.addOnLayoutChangeListener(
                 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
-                        updateCropLocation());
+                        updateImageDimensions());
 
         Intent intent = getIntent();
         mScrollCaptureResponse = intent.getParcelableExtra(EXTRA_CAPTURE_RESPONSE);
@@ -206,7 +206,7 @@
         Log.d(TAG, "onCaptureCompleted(longScreenshot=" + longScreenshot + ")");
         mLongScreenshot = longScreenshot;
         mPreview.setImageDrawable(mLongScreenshot.getDrawable());
-        updateCropLocation();
+        updateImageDimensions();
         mMagnifierView.setDrawable(mLongScreenshot.getDrawable(),
                 mLongScreenshot.getWidth(), mLongScreenshot.getHeight());
         // Original boundaries go from the image tile set's y=0 to y=pageSize, so
@@ -363,10 +363,8 @@
             return;
         }
 
-        Rect bounds = new Rect(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
-        int height = bounds.height();
-        bounds.top = (int) (height * mCropView.getTopBoundary());
-        bounds.bottom = (int) (height * mCropView.getBottomBoundary());
+        Rect bounds = mCropView.getCropBoundaries(drawable.getIntrinsicWidth(),
+                drawable.getIntrinsicHeight());
 
         if (bounds.isEmpty()) {
             Log.w(TAG, "Crop bounds empty, skipping export.");
@@ -404,14 +402,16 @@
         }
     }
 
-    private void updateCropLocation() {
+    private void updateImageDimensions() {
         Drawable drawable = mPreview.getDrawable();
         if (drawable == null) {
             return;
         }
         Rect bounds = drawable.getBounds();
         float imageRatio = bounds.width() / (float) bounds.height();
-        float viewRatio = mPreview.getWidth() / (float) mPreview.getHeight();
+        int previewWidth = mPreview.getWidth() - mPreview.getPaddingLeft()
+                - mPreview.getPaddingRight();
+        float viewRatio = previewWidth / (float) mPreview.getHeight();
 
         if (imageRatio > viewRatio) {
             // Image is full width and height is constrained, compute extra padding to inform
@@ -419,9 +419,12 @@
             float imageHeight = mPreview.getHeight() * viewRatio / imageRatio;
             int extraPadding = (int) (mPreview.getHeight() - imageHeight) / 2;
             mCropView.setExtraPadding(extraPadding, extraPadding);
+            mCropView.setImageWidth(previewWidth);
         } else {
             // Image is full height
             mCropView.setExtraPadding(0, 0);
+            mCropView.setImageWidth((int) (mPreview.getHeight() * imageRatio));
         }
+
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
index 90f3042..08cd91c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
@@ -58,6 +58,7 @@
     private float mCheckerboardBoxSize = 40;
 
     private float mLastCropPosition;
+    private float mLastCenter = 0.5f;
     private CropView.CropBoundary mCropBoundary;
 
     private ViewPropertyAnimator mTranslationAnimator;
@@ -131,7 +132,7 @@
             canvas.save();
             // Translate such that the center of this view represents the center of the crop
             // boundary.
-            canvas.translate(-mDrawable.getBounds().width() / 2 + getWidth() / 2,
+            canvas.translate(-mDrawable.getBounds().width() * mLastCenter + getWidth() / 2,
                     -mDrawable.getBounds().height() * mLastCropPosition + getHeight() / 2);
             mDrawable.draw(canvas);
             canvas.restore();
@@ -148,8 +149,9 @@
 
     @Override
     public void onCropMotionEvent(MotionEvent event, CropView.CropBoundary boundary,
-            float cropPosition, int cropPositionPx) {
+            float cropPosition, int cropPositionPx, float horizontalCenter) {
         mCropBoundary = boundary;
+        mLastCenter = horizontalCenter;
         boolean touchOnRight = event.getX() > getParentWidth() / 2;
         float translateXTarget = touchOnRight ? 0 : getParentWidth() - getWidth();
         switch (event.getAction()) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 5c650ad..d15f1ff 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -142,6 +142,8 @@
     private ScreenshotViewCallback mCallbacks;
     private Animator mDismissAnimation;
     private boolean mPendingSharedTransition;
+    private GestureDetector mSwipeDetector;
+    private SwipeDismissHandler mSwipeDismissHandler;
 
     private final ArrayList<ScreenshotActionChip> mSmartChips = new ArrayList<>();
     private PendingInteraction mPendingInteraction;
@@ -181,6 +183,23 @@
         mContext.getDisplay().getRealMetrics(mDisplayMetrics);
 
         mAccessibilityManager = AccessibilityManager.getInstance(mContext);
+
+        mSwipeDetector = new GestureDetector(mContext,
+                new GestureDetector.SimpleOnGestureListener() {
+                    final Rect mActionsRect = new Rect();
+
+                    @Override
+                    public boolean onScroll(
+                            MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) {
+                        mActionsContainer.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())
+                                || !mActionsContainer.canScrollHorizontally((int) distanceX);
+                    }
+                });
+        mSwipeDetector.setIsLongpressEnabled(false);
+        mSwipeDismissHandler = new SwipeDismissHandler();
     }
 
     /**
@@ -230,6 +249,15 @@
         inoutInfo.touchableRegion.set(touchRegion);
     }
 
+    @Override // ViewGroup
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        // always pass through the down event so the swipe handler knows the initial state
+        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+            mSwipeDismissHandler.onTouch(this, ev);
+        }
+        return mSwipeDetector.onTouchEvent(ev);
+    }
+
     @Override // View
     protected void onFinishInflate() {
         mScreenshotStatic = requireNonNull(findViewById(R.id.global_screenshot_static));
@@ -455,11 +483,7 @@
 
                 createScreenshotActionsShadeAnimation().start();
 
-                SwipeDismissHandler swipeDismissHandler = new SwipeDismissHandler();
-                mScreenshotPreview.setOnTouchListener(swipeDismissHandler);
-                mActionsContainer.setOnTouchListener(swipeDismissHandler);
-                mActionsContainerBackground.setOnTouchListener(swipeDismissHandler);
-                mBackgroundProtection.setOnTouchListener(swipeDismissHandler);
+                setOnTouchListener(mSwipeDismissHandler);
             }
         });
 
@@ -776,30 +800,16 @@
     }
 
     class SwipeDismissHandler implements OnTouchListener {
-
-        // if distance moved on ACTION_UP is less than this, register a click
-        // otherwise, run return animator
-        private static final float CLICK_MOVEMENT_THRESHOLD_DP = 1;
         // distance needed to register a dismissal
         private static final float DISMISS_DISTANCE_THRESHOLD_DP = 30;
 
         private final GestureDetector mGestureDetector;
-        private final float mDismissStartX;
-        private final Rect mActionsRect = new Rect();
 
         private float mStartX;
-        private float mStartY;
-        private float mTranslationX = 0;
-
-        // tracks whether mStartX has been set for this interaction
-        private boolean mInteractionStarted = false;
-        // tracks whether we're dragging the UI (as opposed to scrolling the actions bar)
-        private boolean mIsDragging = false;
 
         SwipeDismissHandler() {
             GestureDetector.OnGestureListener gestureListener = new SwipeDismissGestureListener();
             mGestureDetector = new GestureDetector(mContext, gestureListener);
-            mDismissStartX = mDismissButton.getX();
         }
 
         @Override
@@ -807,13 +817,9 @@
             boolean gestureResult = mGestureDetector.onTouchEvent(event);
             mCallbacks.onUserInteraction();
             if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
-                mInteractionStarted = true;
                 mStartX = event.getRawX();
-                mStartY = event.getRawY();
                 return true;
             } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
-                mInteractionStarted = false;
-                mIsDragging = false;
                 if (isPastDismissThreshold()
                         && (mDismissAnimation == null || !mDismissAnimation.isRunning())) {
                     if (DEBUG_INPUT) {
@@ -821,87 +827,47 @@
                     }
                     mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED);
                     animateDismissal(createSwipeDismissAnimation());
-                    return true;
-                } else if (MathUtils.dist(mStartX, mStartY, event.getRawX(), event.getRawY())
-                        > dpToPx(CLICK_MOVEMENT_THRESHOLD_DP)) {
-                    // if we've moved a non-negligible distance (but not past the threshold),
+                } else {
+                    // if we've moved, but not past the threshold, start the return animation
                     if (DEBUG_DISMISS) {
                         Log.d(TAG, "swipe gesture abandoned");
                     }
-                    // start the return animation
                     if ((mDismissAnimation == null || !mDismissAnimation.isRunning())) {
                         createSwipeReturnAnimation().start();
                     }
-                    return true;
-                } else {
-                    if (view == mScreenshotPreview) {
-                        mScreenshotPreview.performClick();
-                    }
                 }
+                return true;
             }
             return gestureResult;
         }
 
         class SwipeDismissGestureListener extends GestureDetector.SimpleOnGestureListener {
-
-            /**
-             * This is somewhat complicated to handle because we want to allow scrolling the actions
-             * bar (if it extends off the screen) as well as dismissing the UI horizontally by
-             * dragging the actions bar. In addition, we don't get the initial ACTION_DOWN because
-             * it is consumed by the action bar view.
-             *
-             * So, we use a gated system: first, keep track of the pointer location as we move;
-             * next, check whether the actions bar can scroll in the direction we're moving in. If
-             * it can, let the actions bar handle the event; otherwise, we've gone as far as we can
-             * and can start dragging the UI instead.
-             */
             @Override
             public boolean onScroll(
                     MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) {
-                mActionsContainer.getBoundsOnScreen(mActionsRect);
-                if (!mInteractionStarted) {
-                    if (mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY())) {
-                        mStartX = ev2.getRawX();
-                        mInteractionStarted = true;
-                    }
-                } else {
-                    float distance = ev2.getRawX() - mStartX;
-                    if ((mIsDragging && distance * mTranslationX > 0)
-                            || !mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY())
-                            || !mActionsContainer.canScrollHorizontally(-1 * (int) distance)) {
-                        mIsDragging = true;
-                        mTranslationX = distance;
-                        mScreenshotStatic.setTranslationX(mTranslationX);
-                        return true;
-                    } else {
-                        mStartX = ev2.getRawX();
-                    }
-                }
-                return false;
+                mScreenshotStatic.setTranslationX(ev2.getRawX() - mStartX);
+                return true;
             }
         }
 
         private boolean isPastDismissThreshold() {
-            if (mDirectionLTR) {
-                return mTranslationX <= -1 * dpToPx(DISMISS_DISTANCE_THRESHOLD_DP);
-            } else {
-                return mTranslationX >= dpToPx(DISMISS_DISTANCE_THRESHOLD_DP);
-            }
+            float distance = Math.abs(mScreenshotStatic.getTranslationX());
+            return distance >= dpToPx(DISMISS_DISTANCE_THRESHOLD_DP);
         }
 
         private ValueAnimator createSwipeDismissAnimation() {
             ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
-            float startX = mTranslationX;
-            float finalX = mDirectionLTR
-                    ? -1 * (mActionsContainerBackground.getX()
-                    + mActionsContainerBackground.getWidth())
+            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;
 
             anim.addUpdateListener(animation -> {
-                float translation = MathUtils.lerp(startX, finalX,
-                        animation.getAnimatedFraction());
+                float translation = MathUtils.lerp(startX, finalX, animation.getAnimatedFraction());
                 mScreenshotStatic.setTranslationX(translation);
-
                 setAlpha(1 - animation.getAnimatedFraction());
             });
             anim.setDuration(400);
@@ -910,9 +876,8 @@
 
         private ValueAnimator createSwipeReturnAnimation() {
             ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
-            float startX = mTranslationX;
+            float startX = mScreenshotStatic.getTranslationX();
             float finalX = 0;
-            mTranslationX = 0;
 
             anim.addUpdateListener(animation -> {
                 float translation = MathUtils.lerp(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index ca3923f..c565a271 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -38,8 +38,6 @@
 import android.os.AsyncTask;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.provider.DeviceConfig;
-import android.provider.DeviceConfig.Properties;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationStats;
 import android.service.notification.StatusBarNotification;
@@ -48,7 +46,6 @@
 import android.view.View;
 import android.widget.ImageView;
 
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
@@ -147,23 +144,6 @@
     private ImageView mBackdropFront;
     private ImageView mBackdropBack;
 
-    private boolean mShowCompactMediaSeekbar;
-    private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener =
-            new DeviceConfig.OnPropertiesChangedListener() {
-        @Override
-        public void onPropertiesChanged(Properties properties) {
-            for (String name : properties.getKeyset()) {
-                if (SystemUiDeviceConfigFlags.COMPACT_MEDIA_SEEKBAR_ENABLED.equals(name)) {
-                    String value = properties.getString(name, null);
-                    if (DEBUG_MEDIA) {
-                        Log.v(TAG, "DEBUG_MEDIA: compact media seekbar flag updated: " + value);
-                    }
-                    mShowCompactMediaSeekbar = "true".equals(value);
-                }
-            }
-        }
-    };
-
     private final MediaController.Callback mMediaListener = new MediaController.Callback() {
         @Override
         public void onPlaybackStateChanged(PlaybackState state) {
@@ -231,14 +211,6 @@
             setupNotifPipeline();
             mUsingNotifPipeline = true;
         }
-
-        mShowCompactMediaSeekbar = "true".equals(
-                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
-                    SystemUiDeviceConfigFlags.COMPACT_MEDIA_SEEKBAR_ENABLED));
-
-        deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
-                mContext.getMainExecutor(),
-                mPropertiesChangedListener);
     }
 
     private void setupNotifPipeline() {
@@ -405,10 +377,6 @@
         return mMediaMetadata;
     }
 
-    public boolean getShowCompactMediaSeekbar() {
-        return mShowCompactMediaSeekbar;
-    }
-
     public Icon getMediaIcon() {
         if (mMediaNotificationKey == null) {
             return null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
index 2aba103..cc7a4f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
@@ -16,7 +16,6 @@
 
 import android.content.Context;
 import android.graphics.Rect;
-import android.net.ConnectivityManager;
 import android.os.Bundle;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
@@ -123,8 +122,7 @@
                 .getValue(KEY_SHOW_OPERATOR_NAME, 1) != 0;
         setVisibility(showOperatorName ? VISIBLE : GONE);
 
-        boolean hasMobile = ConnectivityManager.from(mContext)
-                .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+        boolean hasMobile = mContext.getSystemService(TelephonyManager.class).isDataCapable();
         boolean airplaneMode = WirelessUtils.isAirplaneModeOn(mContext);
         if (!hasMobile || airplaneMode) {
             setText(null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageGradientColorizer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageGradientColorizer.java
deleted file mode 100644
index f5a76f0..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageGradientColorizer.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.LinearGradient;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Shader;
-import android.graphics.drawable.Drawable;
-
-/**
- * A utility class to colorize bitmaps with a color gradient and a special blending mode
- */
-public class ImageGradientColorizer {
-    public Bitmap colorize(Drawable drawable, int backgroundColor, boolean isRtl) {
-        int width = drawable.getIntrinsicWidth();
-        int height = drawable.getIntrinsicHeight();
-        int size = Math.min(width, height);
-        int widthInset = (width - size) / 2;
-        int heightInset = (height - size) / 2;
-        drawable = drawable.mutate();
-        drawable.setBounds(- widthInset, - heightInset, width - widthInset, height - heightInset);
-        Bitmap newBitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
-        Canvas canvas = new Canvas(newBitmap);
-
-        // Values to calculate the luminance of a color
-        float lr = 0.2126f;
-        float lg = 0.7152f;
-        float lb = 0.0722f;
-
-        // Extract the red, green, blue components of the color extraction color in
-        // float and int form
-        int tri = Color.red(backgroundColor);
-        int tgi = Color.green(backgroundColor);
-        int tbi = Color.blue(backgroundColor);
-
-        float tr = tri / 255f;
-        float tg = tgi / 255f;
-        float tb = tbi / 255f;
-
-        // Calculate the luminance of the color extraction color
-        float cLum = (tr * lr + tg * lg + tb * lb) * 255;
-
-        ColorMatrix m = new ColorMatrix(new float[] {
-                lr, lg, lb, 0, tri - cLum,
-                lr, lg, lb, 0, tgi - cLum,
-                lr, lg, lb, 0, tbi - cLum,
-                0, 0, 0, 1, 0,
-        });
-
-        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        LinearGradient linearGradient =  new LinearGradient(0, 0, size, 0,
-                new int[] {0, Color.argb(0.5f, 1, 1, 1), Color.BLACK},
-                new float[] {0.0f, 0.4f, 1.0f}, Shader.TileMode.CLAMP);
-        paint.setShader(linearGradient);
-        Bitmap fadeIn = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
-        Canvas fadeInCanvas = new Canvas(fadeIn);
-        drawable.clearColorFilter();
-        drawable.draw(fadeInCanvas);
-
-        if (isRtl) {
-            // Let's flip the gradient
-            fadeInCanvas.translate(size, 0);
-            fadeInCanvas.scale(-1, 1);
-        }
-        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
-        fadeInCanvas.drawPaint(paint);
-
-        Paint coloredPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        coloredPaint.setColorFilter(new ColorMatrixColorFilter(m));
-        coloredPaint.setAlpha((int) (0.5f * 255));
-        canvas.drawBitmap(fadeIn, 0, 0, coloredPaint);
-
-        linearGradient =  new LinearGradient(0, 0, size, 0,
-                new int[] {0, Color.argb(0.5f, 1, 1, 1), Color.BLACK},
-                new float[] {0.0f, 0.6f, 1.0f}, Shader.TileMode.CLAMP);
-        paint.setShader(linearGradient);
-        fadeInCanvas.drawPaint(paint);
-        canvas.drawBitmap(fadeIn, 0, 0, null);
-
-        return newBitmap;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java
index 2586e94..732c115 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java
@@ -16,237 +16,30 @@
 
 package com.android.systemui.statusbar.notification;
 
-import android.app.Notification;
-import android.content.Context;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.util.LayoutDirection;
 
-import androidx.annotation.VisibleForTesting;
 import androidx.palette.graphics.Palette;
 
-import com.android.internal.util.ContrastColorUtil;
-import com.android.settingslib.Utils;
-
 import java.util.List;
 
 /**
- * A class the processes media notifications and extracts the right text and background colors.
+ * A gutted class that now contains only a color extraction utility used by the
+ * MediaArtworkProcessor, which has otherwise supplanted this.
+ *
+ * TODO(b/182926117): move this into MediaArtworkProcessor.kt
  */
 public class MediaNotificationProcessor {
 
     /**
-     * The fraction below which we select the vibrant instead of the light/dark vibrant color
-     */
-    private static final float POPULATION_FRACTION_FOR_MORE_VIBRANT = 1.0f;
-
-    /**
-     * Minimum saturation that a muted color must have if there exists if deciding between two
-     * colors
-     */
-    private static final float MIN_SATURATION_WHEN_DECIDING = 0.19f;
-
-    /**
-     * Minimum fraction that any color must have to be picked up as a text color
-     */
-    private static final double MINIMUM_IMAGE_FRACTION = 0.002;
-
-    /**
-     * The population fraction to select the dominant color as the text color over a the colored
-     * ones.
-     */
-    private static final float POPULATION_FRACTION_FOR_DOMINANT = 0.01f;
-
-    /**
      * The population fraction to select a white or black color as the background over a color.
      */
     private static final float POPULATION_FRACTION_FOR_WHITE_OR_BLACK = 2.5f;
     private static final float BLACK_MAX_LIGHTNESS = 0.08f;
     private static final float WHITE_MIN_LIGHTNESS = 0.90f;
     private static final int RESIZE_BITMAP_AREA = 150 * 150;
-    private final ImageGradientColorizer mColorizer;
-    private final Context mContext;
-    private final Palette.Filter mBlackWhiteFilter = (rgb, hsl) -> !isWhiteOrBlack(hsl);
 
-    /**
-     * The context of the notification. This is the app context of the package posting the
-     * notification.
-     */
-    private final Context mPackageContext;
-
-    public MediaNotificationProcessor(Context context, Context packageContext) {
-        this(context, packageContext, new ImageGradientColorizer());
-    }
-
-    @VisibleForTesting
-    MediaNotificationProcessor(Context context, Context packageContext,
-            ImageGradientColorizer colorizer) {
-        mContext = context;
-        mPackageContext = packageContext;
-        mColorizer = colorizer;
-    }
-
-    /**
-     * Processes a builder of a media notification and calculates the appropriate colors that should
-     * be used.
-     *
-     * @param notification the notification that is being processed
-     * @param builder the recovered builder for the notification. this will be modified
-     */
-    public void processNotification(Notification notification, Notification.Builder builder) {
-        Icon largeIcon = notification.getLargeIcon();
-        Bitmap bitmap = null;
-        Drawable drawable = null;
-        if (largeIcon != null) {
-            // We're transforming the builder, let's make sure all baked in RemoteViews are
-            // rebuilt!
-            builder.setRebuildStyledRemoteViews(true);
-            drawable = largeIcon.loadDrawable(mPackageContext);
-            int backgroundColor = 0;
-            if (notification.isColorizedMedia()) {
-                int width = drawable.getIntrinsicWidth();
-                int height = drawable.getIntrinsicHeight();
-                int area = width * height;
-                if (area > RESIZE_BITMAP_AREA) {
-                    double factor = Math.sqrt((float) RESIZE_BITMAP_AREA / area);
-                    width = (int) (factor * width);
-                    height = (int) (factor * height);
-                }
-                bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-                Canvas canvas = new Canvas(bitmap);
-                drawable.setBounds(0, 0, width, height);
-                drawable.draw(canvas);
-
-                Palette.Builder paletteBuilder = generateArtworkPaletteBuilder(bitmap);
-                Palette palette = paletteBuilder.generate();
-                Palette.Swatch backgroundSwatch = findBackgroundSwatch(palette);
-                backgroundColor = backgroundSwatch.getRgb();
-                // we want most of the full region again, slightly shifted to the right
-                float textColorStartWidthFraction = 0.4f;
-                paletteBuilder.setRegion((int) (bitmap.getWidth() * textColorStartWidthFraction), 0,
-                        bitmap.getWidth(),
-                        bitmap.getHeight());
-                // We're not filtering on white or black
-                if (!isWhiteOrBlack(backgroundSwatch.getHsl())) {
-                    final float backgroundHue = backgroundSwatch.getHsl()[0];
-                    paletteBuilder.addFilter((rgb, hsl) -> {
-                        // at least 10 degrees hue difference
-                        float diff = Math.abs(hsl[0] - backgroundHue);
-                        return diff > 10 && diff < 350;
-                    });
-                }
-                paletteBuilder.addFilter(mBlackWhiteFilter);
-                palette = paletteBuilder.generate();
-                int foregroundColor = selectForegroundColor(backgroundColor, palette);
-                builder.setColorPalette(backgroundColor, foregroundColor);
-            } else {
-                backgroundColor = Utils.getColorAttr(mContext, android.R.attr.colorBackground)
-                        .getDefaultColor();
-            }
-            Bitmap colorized = mColorizer.colorize(drawable, backgroundColor,
-                    mContext.getResources().getConfiguration().getLayoutDirection() ==
-                            LayoutDirection.RTL);
-            builder.setLargeIcon(Icon.createWithBitmap(colorized));
-        }
-    }
-
-    /**
-     * Select a foreground color depending on whether the background color is dark or light
-     * @param backgroundColor Background color to coordinate with
-     * @param palette Artwork palette, should be obtained from {@link generateArtworkPaletteBuilder}
-     * @return foreground color
-     */
-    public static int selectForegroundColor(int backgroundColor, Palette palette) {
-        if (ContrastColorUtil.isColorLight(backgroundColor)) {
-            return selectForegroundColorForSwatches(palette.getDarkVibrantSwatch(),
-                    palette.getVibrantSwatch(),
-                    palette.getDarkMutedSwatch(),
-                    palette.getMutedSwatch(),
-                    palette.getDominantSwatch(),
-                    Color.BLACK);
-        } else {
-            return selectForegroundColorForSwatches(palette.getLightVibrantSwatch(),
-                    palette.getVibrantSwatch(),
-                    palette.getLightMutedSwatch(),
-                    palette.getMutedSwatch(),
-                    palette.getDominantSwatch(),
-                    Color.WHITE);
-        }
-    }
-
-    private static int selectForegroundColorForSwatches(Palette.Swatch moreVibrant,
-            Palette.Swatch vibrant, Palette.Swatch moreMutedSwatch, Palette.Swatch mutedSwatch,
-            Palette.Swatch dominantSwatch, int fallbackColor) {
-        Palette.Swatch coloredCandidate = selectVibrantCandidate(moreVibrant, vibrant);
-        if (coloredCandidate == null) {
-            coloredCandidate = selectMutedCandidate(mutedSwatch, moreMutedSwatch);
-        }
-        if (coloredCandidate != null) {
-            if (dominantSwatch == coloredCandidate) {
-                return coloredCandidate.getRgb();
-            } else if ((float) coloredCandidate.getPopulation() / dominantSwatch.getPopulation()
-                    < POPULATION_FRACTION_FOR_DOMINANT
-                    && dominantSwatch.getHsl()[1] > MIN_SATURATION_WHEN_DECIDING) {
-                return dominantSwatch.getRgb();
-            } else {
-                return coloredCandidate.getRgb();
-            }
-        } else if (hasEnoughPopulation(dominantSwatch)) {
-            return dominantSwatch.getRgb();
-        } else {
-            return fallbackColor;
-        }
-    }
-
-    private static Palette.Swatch selectMutedCandidate(Palette.Swatch first,
-            Palette.Swatch second) {
-        boolean firstValid = hasEnoughPopulation(first);
-        boolean secondValid = hasEnoughPopulation(second);
-        if (firstValid && secondValid) {
-            float firstSaturation = first.getHsl()[1];
-            float secondSaturation = second.getHsl()[1];
-            float populationFraction = first.getPopulation() / (float) second.getPopulation();
-            if (firstSaturation * populationFraction > secondSaturation) {
-                return first;
-            } else {
-                return second;
-            }
-        } else if (firstValid) {
-            return first;
-        } else if (secondValid) {
-            return second;
-        }
-        return null;
-    }
-
-    private static Palette.Swatch selectVibrantCandidate(Palette.Swatch first,
-            Palette.Swatch second) {
-        boolean firstValid = hasEnoughPopulation(first);
-        boolean secondValid = hasEnoughPopulation(second);
-        if (firstValid && secondValid) {
-            int firstPopulation = first.getPopulation();
-            int secondPopulation = second.getPopulation();
-            if (firstPopulation / (float) secondPopulation
-                    < POPULATION_FRACTION_FOR_MORE_VIBRANT) {
-                return second;
-            } else {
-                return first;
-            }
-        } else if (firstValid) {
-            return first;
-        } else if (secondValid) {
-            return second;
-        }
-        return null;
-    }
-
-    private static boolean hasEnoughPopulation(Palette.Swatch swatch) {
-        // We want a fraction that is at least 1% of the image
-        return swatch != null
-                && (swatch.getPopulation() / (float) RESIZE_BITMAP_AREA > MINIMUM_IMAGE_FRACTION);
+    private MediaNotificationProcessor() {
     }
 
     /**
@@ -279,7 +72,7 @@
         List<Palette.Swatch> swatches = palette.getSwatches();
         float highestNonWhitePopulation = -1;
         Palette.Swatch second = null;
-        for (Palette.Swatch swatch: swatches) {
+        for (Palette.Swatch swatch : swatches) {
             if (swatch != dominantSwatch
                     && swatch.getPopulation() > highestNonWhitePopulation
                     && !isWhiteOrBlack(swatch.getHsl())) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 6cf5c30..815cfb3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -668,7 +668,6 @@
                 && expandedView.findViewById(com.android.internal.R.id.media_actions) != null;
         boolean isMessagingLayout = contractedView instanceof MessagingLayout;
         boolean isCallLayout = contractedView instanceof CallLayout;
-        boolean showCompactMediaSeekbar = mMediaManager.getShowCompactMediaSeekbar();
 
         if (customView && beforeS && !mIsSummaryWithChildren) {
             if (beforeN) {
@@ -678,12 +677,6 @@
             } else {
                 smallHeight = mMaxSmallHeightBeforeS;
             }
-        } else if (isMediaLayout) {
-            // TODO(b/172652345): MediaStyle notifications currently look broken when we enforce
-            //  the standard notification height, so we have to afford them more vertical space to
-            //  make sure we don't crop them terribly.  We actually need to revisit this and give
-            //  them a headerless design, then remove this hack.
-            smallHeight = showCompactMediaSeekbar ? mMaxSmallHeightMedia : mMaxSmallHeightBeforeS;
         } else if (isMessagingLayout) {
             // TODO(b/173204301): MessagingStyle notifications currently look broken when we enforce
             //  the standard notification height, so we have to afford them more vertical space to
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 58b87cd..73c4b05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -40,13 +40,11 @@
 import com.android.internal.widget.ImageMessageConsumer;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.media.MediaDataManagerKt;
 import com.android.systemui.media.MediaFeatureFlag;
 import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
 import com.android.systemui.statusbar.notification.InflationException;
-import com.android.systemui.statusbar.notification.MediaNotificationProcessor;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -799,13 +797,6 @@
                     // For all of our templates, we want it to be RTL
                     packageContext = new RtlEnabledContext(packageContext);
                 }
-                Notification notification = sbn.getNotification();
-                if (notification.isMediaNotification() && !(mIsMediaInQS
-                        && MediaDataManagerKt.isMediaNotification(sbn))) {
-                    MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext,
-                            packageContext);
-                    processor.processNotification(notification, recoveredBuilder);
-                }
                 if (mEntry.getRanking().isConversation()) {
                     mConversationProcessor.processNotification(mEntry, recoveredBuilder);
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
index 2535e5d..c75cd78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
@@ -16,341 +16,26 @@
 
 package com.android.systemui.statusbar.notification.row.wrapper;
 
-import static com.android.systemui.Dependency.MAIN_HANDLER;
-
-import android.annotation.Nullable;
-import android.app.Notification;
 import android.content.Context;
-import android.content.res.ColorStateList;
-import android.media.MediaMetadata;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
-import android.metrics.LogMaker;
-import android.os.Handler;
-import android.text.format.DateUtils;
-import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewStub;
-import android.widget.SeekBar;
-import android.widget.TextView;
 
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.widget.MediaNotificationView;
-import com.android.systemui.Dependency;
-import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.TransformableView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
-import java.util.Timer;
-import java.util.TimerTask;
-
 /**
  * Wraps a notification containing a media template
  */
 public class NotificationMediaTemplateViewWrapper extends NotificationTemplateViewWrapper {
 
-    private static final long PROGRESS_UPDATE_INTERVAL = 1000; // 1s
-    private static final String COMPACT_MEDIA_TAG = "media";
-    private final Handler mHandler = Dependency.get(MAIN_HANDLER);
-    private Timer mSeekBarTimer;
     private View mActions;
-    private SeekBar mSeekBar;
-    private TextView mSeekBarElapsedTime;
-    private TextView mSeekBarTotalTime;
-    private long mDuration = 0;
-    private MediaController mMediaController;
-    private MediaMetadata mMediaMetadata;
-    private NotificationMediaManager mMediaManager;
-    private View mSeekBarView;
-    private Context mContext;
-    private MetricsLogger mMetricsLogger;
-    private boolean mIsViewVisible;
-
-    @VisibleForTesting
-    protected SeekBar.OnSeekBarChangeListener mSeekListener =
-            new SeekBar.OnSeekBarChangeListener() {
-        @Override
-        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-        }
-
-        @Override
-        public void onStartTrackingTouch(SeekBar seekBar) {
-        }
-
-        @Override
-        public void onStopTrackingTouch(SeekBar seekBar) {
-            if (mMediaController != null) {
-                mMediaController.getTransportControls().seekTo(mSeekBar.getProgress());
-                mMetricsLogger.write(newLog(MetricsEvent.TYPE_UPDATE));
-            }
-        }
-    };
-
-    private MediaNotificationView.VisibilityChangeListener mVisibilityListener =
-            new MediaNotificationView.VisibilityChangeListener() {
-        @Override
-        public void onAggregatedVisibilityChanged(boolean isVisible) {
-            mIsViewVisible = isVisible;
-            if (isVisible && mMediaController != null) {
-                // Restart timer if we're currently playing and didn't already have one going
-                PlaybackState state = mMediaController.getPlaybackState();
-                if (state != null && state.getState() == PlaybackState.STATE_PLAYING
-                        && mSeekBarTimer == null && mSeekBarView != null
-                        && mSeekBarView.getVisibility() != View.GONE) {
-                    startTimer();
-                }
-            } else {
-                clearTimer();
-            }
-        }
-    };
-
-    private View.OnAttachStateChangeListener mAttachStateListener =
-            new View.OnAttachStateChangeListener() {
-        @Override
-        public void onViewAttachedToWindow(View v) {
-        }
-
-        @Override
-        public void onViewDetachedFromWindow(View v) {
-            mIsViewVisible = false;
-        }
-    };
-
-    private MediaController.Callback mMediaCallback = new MediaController.Callback() {
-        @Override
-        public void onSessionDestroyed() {
-            clearTimer();
-            mMediaController.unregisterCallback(this);
-            if (mView instanceof MediaNotificationView) {
-                ((MediaNotificationView) mView).removeVisibilityListener(mVisibilityListener);
-                mView.removeOnAttachStateChangeListener(mAttachStateListener);
-            }
-        }
-
-        @Override
-        public void onPlaybackStateChanged(@Nullable PlaybackState state) {
-            if (state == null) {
-                return;
-            }
-
-            if (state.getState() != PlaybackState.STATE_PLAYING) {
-                // Update the UI once, in case playback info changed while we were paused
-                updatePlaybackUi(state);
-                clearTimer();
-            } else if (mSeekBarTimer == null && mSeekBarView != null
-                    && mSeekBarView.getVisibility() != View.GONE) {
-                startTimer();
-            }
-        }
-
-        @Override
-        public void onMetadataChanged(@Nullable MediaMetadata metadata) {
-            if (mMediaMetadata == null || !mMediaMetadata.equals(metadata)) {
-                mMediaMetadata = metadata;
-                updateDuration();
-            }
-        }
-    };
 
     protected NotificationMediaTemplateViewWrapper(Context ctx, View view,
             ExpandableNotificationRow row) {
         super(ctx, view, row);
-        mContext = ctx;
-        mMediaManager = Dependency.get(NotificationMediaManager.class);
-        mMetricsLogger = Dependency.get(MetricsLogger.class);
     }
 
     private void resolveViews() {
         mActions = mView.findViewById(com.android.internal.R.id.media_actions);
-        mIsViewVisible = mView.isShown();
-
-        final MediaSession.Token token = mRow.getEntry().getSbn().getNotification().extras
-                .getParcelable(Notification.EXTRA_MEDIA_SESSION);
-
-        boolean showCompactSeekbar = mMediaManager.getShowCompactMediaSeekbar();
-        if (token == null || (COMPACT_MEDIA_TAG.equals(mView.getTag()) && !showCompactSeekbar)) {
-            if (mSeekBarView != null) {
-                mSeekBarView.setVisibility(View.GONE);
-            }
-            return;
-        }
-
-        // Check for existing media controller and clean up / create as necessary
-        boolean shouldUpdateListeners = false;
-        if (mMediaController == null || !mMediaController.getSessionToken().equals(token)) {
-            if (mMediaController != null) {
-                mMediaController.unregisterCallback(mMediaCallback);
-            }
-            mMediaController = new MediaController(mContext, token);
-            shouldUpdateListeners = true;
-        }
-
-        mMediaMetadata = mMediaController.getMetadata();
-        if (mMediaMetadata != null) {
-            long duration = mMediaMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
-            if (duration <= 0) {
-                // Don't include the seekbar if this is a livestream
-                if (mSeekBarView != null && mSeekBarView.getVisibility() != View.GONE) {
-                    mSeekBarView.setVisibility(View.GONE);
-                    mMetricsLogger.write(newLog(MetricsEvent.TYPE_CLOSE));
-                    clearTimer();
-                } else if (mSeekBarView == null && shouldUpdateListeners) {
-                    // Only log if the controller changed, otherwise we would log multiple times for
-                    // the same notification when user pauses/resumes
-                    mMetricsLogger.write(newLog(MetricsEvent.TYPE_CLOSE));
-                }
-                return;
-            } else if (mSeekBarView != null && mSeekBarView.getVisibility() == View.GONE) {
-                // Otherwise, make sure the seekbar is visible
-                mSeekBarView.setVisibility(View.VISIBLE);
-                mMetricsLogger.write(newLog(MetricsEvent.TYPE_OPEN));
-                updateDuration();
-                startTimer();
-            }
-        }
-
-        // Inflate the seekbar template
-        ViewStub stub = mView.findViewById(R.id.notification_media_seekbar_container);
-        if (stub instanceof ViewStub) {
-            LayoutInflater layoutInflater = LayoutInflater.from(stub.getContext());
-            stub.setLayoutInflater(layoutInflater);
-            stub.setLayoutResource(R.layout.notification_material_media_seekbar);
-            mSeekBarView = stub.inflate();
-            mMetricsLogger.write(newLog(MetricsEvent.TYPE_OPEN));
-
-            mSeekBar = mSeekBarView.findViewById(R.id.notification_media_progress_bar);
-            mSeekBar.setOnSeekBarChangeListener(mSeekListener);
-
-            mSeekBarElapsedTime = mSeekBarView.findViewById(R.id.notification_media_elapsed_time);
-            mSeekBarTotalTime = mSeekBarView.findViewById(R.id.notification_media_total_time);
-
-            shouldUpdateListeners = true;
-        }
-
-        if (shouldUpdateListeners) {
-            if (mView instanceof MediaNotificationView) {
-                MediaNotificationView mediaView = (MediaNotificationView) mView;
-                mediaView.addVisibilityListener(mVisibilityListener);
-                mView.addOnAttachStateChangeListener(mAttachStateListener);
-            }
-
-            if (mSeekBarTimer == null) {
-                if (mMediaController != null && canSeekMedia(mMediaController.getPlaybackState())) {
-                    // Log initial state, since it will not be updated
-                    mMetricsLogger.write(newLog(MetricsEvent.TYPE_DETAIL, 1));
-                } else {
-                    setScrubberVisible(false);
-                }
-                updateDuration();
-                startTimer();
-                mMediaController.registerCallback(mMediaCallback);
-            }
-        }
-        updateSeekBarTint(mSeekBarView);
-    }
-
-    private void startTimer() {
-        clearTimer();
-        if (mIsViewVisible) {
-            mSeekBarTimer = new Timer(true /* isDaemon */);
-            mSeekBarTimer.schedule(new TimerTask() {
-                @Override
-                public void run() {
-                    mHandler.post(mOnUpdateTimerTick);
-                }
-            }, 0, PROGRESS_UPDATE_INTERVAL);
-        }
-    }
-
-    private void clearTimer() {
-        if (mSeekBarTimer != null) {
-            mSeekBarTimer.cancel();
-            mSeekBarTimer.purge();
-            mSeekBarTimer = null;
-        }
-    }
-
-    @Override
-    public void setRemoved() {
-        clearTimer();
-        if (mMediaController != null) {
-            mMediaController.unregisterCallback(mMediaCallback);
-        }
-        if (mView instanceof MediaNotificationView) {
-            ((MediaNotificationView) mView).removeVisibilityListener(mVisibilityListener);
-            mView.removeOnAttachStateChangeListener(mAttachStateListener);
-        }
-    }
-
-    private boolean canSeekMedia(@Nullable PlaybackState state) {
-        if (state == null) {
-            return false;
-        }
-
-        long actions = state.getActions();
-        return ((actions & PlaybackState.ACTION_SEEK_TO) != 0);
-    }
-
-    private void setScrubberVisible(boolean isVisible) {
-        if (mSeekBar == null || mSeekBar.isEnabled() == isVisible) {
-            return;
-        }
-
-        mSeekBar.getThumb().setAlpha(isVisible ? 255 : 0);
-        mSeekBar.setEnabled(isVisible);
-        mMetricsLogger.write(newLog(MetricsEvent.TYPE_DETAIL, isVisible ? 1 : 0));
-    }
-
-    private void updateDuration() {
-        if (mMediaMetadata != null && mSeekBar != null) {
-            long duration = mMediaMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
-            if (mDuration != duration) {
-                mDuration = duration;
-                mSeekBar.setMax((int) mDuration);
-                mSeekBarTotalTime.setText(millisecondsToTimeString(duration));
-            }
-        }
-    }
-
-    protected final Runnable mOnUpdateTimerTick = new Runnable() {
-        @Override
-        public void run() {
-            if (mMediaController != null && mSeekBar != null) {
-                PlaybackState playbackState = mMediaController.getPlaybackState();
-                if (playbackState != null) {
-                    updatePlaybackUi(playbackState);
-                } else {
-                    clearTimer();
-                }
-            } else {
-                clearTimer();
-            }
-        }
-    };
-
-    private void updatePlaybackUi(PlaybackState state) {
-        if (mSeekBar == null || mSeekBarElapsedTime == null) {
-            return;
-        }
-
-        long position = state.getPosition();
-        mSeekBar.setProgress((int) position);
-
-        mSeekBarElapsedTime.setText(millisecondsToTimeString(position));
-
-        // Update scrubber in case available actions have changed
-        setScrubberVisible(canSeekMedia(state));
-    }
-
-    private String millisecondsToTimeString(long milliseconds) {
-        long seconds = milliseconds / 1000;
-        String text = DateUtils.formatElapsedTime(seconds);
-        return text;
     }
 
     @Override
@@ -361,28 +46,6 @@
         super.onContentUpdated(row);
     }
 
-    private void updateSeekBarTint(View seekBarContainer) {
-        if (seekBarContainer == null) {
-            return;
-        }
-
-        if (this.getNotificationHeader() == null) {
-            return;
-        }
-
-        int tintColor = getOriginalIconColor();
-        mSeekBarElapsedTime.setTextColor(tintColor);
-        mSeekBarTotalTime.setTextColor(tintColor);
-        mSeekBarTotalTime.setShadowLayer(1.5f, 1.5f, 1.5f, mBackgroundColor);
-
-        ColorStateList tintList = ColorStateList.valueOf(tintColor);
-        mSeekBar.setThumbTintList(tintList);
-        tintList = tintList.withAlpha(192); // 75%
-        mSeekBar.setProgressTintList(tintList);
-        tintList = tintList.withAlpha(128); // 50%
-        mSeekBar.setProgressBackgroundTintList(tintList);
-    }
-
     @Override
     protected void updateTransformedTypes() {
         // This also clears the existing types
@@ -394,36 +57,7 @@
     }
 
     @Override
-    public boolean isDimmable() {
-        return getCustomBackgroundColor() == 0;
-    }
-
-    @Override
     public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) {
         return true;
     }
-
-    /**
-     * Returns an initialized LogMaker for logging changes to the seekbar
-     * @return new LogMaker
-     */
-    private LogMaker newLog(int event) {
-        String packageName = mRow.getEntry().getSbn().getPackageName();
-
-        return new LogMaker(MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR)
-                .setType(event)
-                .setPackageName(packageName);
-    }
-
-    /**
-     * Returns an initialized LogMaker for logging changes with subtypes
-     * @return new LogMaker
-     */
-    private LogMaker newLog(int event, int subtype) {
-        String packageName = mRow.getEntry().getSbn().getPackageName();
-        return new LogMaker(MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR)
-                .setType(event)
-                .setSubtype(subtype)
-                .setPackageName(packageName);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 39f5847c..562d0ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -42,6 +42,9 @@
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Contains the collapsed status bar and handles hiding/showing based on disable flags
  * and keyguard state. Also manages lifecycle to make sure the views it contains are being
@@ -70,6 +73,8 @@
     private View mOperatorNameFrame;
     private CommandQueue mCommandQueue;
 
+    private List<String> mBlockedIcons = new ArrayList<>();
+
     private SignalCallback mSignalCallback = new SignalCallback() {
         @Override
         public void setIsAirplaneMode(NetworkController.IconState icon) {
@@ -101,9 +106,12 @@
             mStatusBar.restoreHierarchyState(
                     savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE));
         }
-        mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons),
-                Dependency.get(CommandQueue.class));
+        mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons));
         mDarkIconManager.setShouldLog(true);
+        mBlockedIcons.add(getString(com.android.internal.R.string.status_bar_volume));
+        mBlockedIcons.add(getString(com.android.internal.R.string.status_bar_alarm_clock));
+        mBlockedIcons.add(getString(com.android.internal.R.string.status_bar_call_strength));
+        mDarkIconManager.setBlockList(mBlockedIcons);
         Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);
         mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
         mClockView = mStatusBar.findViewById(R.id.clock);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 33798d6..2d760e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -47,7 +47,6 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -59,6 +58,8 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * The header group on Keyguard.
@@ -89,6 +90,7 @@
     private int mSystemIconsBaseMargin;
     private View mSystemIconsContainer;
     private TintedIconManager mIconManager;
+    private List<String> mBlockedIcons = new ArrayList<>();
 
     private View mCutoutSpace;
     private ViewGroup mStatusIconArea;
@@ -121,6 +123,7 @@
         mStatusIconContainer = findViewById(R.id.statusIcons);
 
         loadDimens();
+        loadBlockList();
         mBatteryController = Dependency.get(BatteryController.class);
     }
 
@@ -181,6 +184,14 @@
                 R.dimen.rounded_corner_content_padding);
     }
 
+    // Set hidden status bar items
+    private void loadBlockList() {
+        Resources r = getResources();
+        mBlockedIcons.add(r.getString(com.android.internal.R.string.status_bar_volume));
+        mBlockedIcons.add(r.getString(com.android.internal.R.string.status_bar_alarm_clock));
+        mBlockedIcons.add(r.getString(com.android.internal.R.string.status_bar_call_strength));
+    }
+
     private void updateVisibilities() {
         if (mMultiUserAvatar.getParent() != mStatusIconArea
                 && !mKeyguardUserSwitcherEnabled) {
@@ -336,8 +347,8 @@
         userInfoController.addCallback(this);
         userInfoController.reloadUserInfo();
         Dependency.get(ConfigurationController.class).addCallback(this);
-        mIconManager = new TintedIconManager(findViewById(R.id.statusIcons),
-                Dependency.get(CommandQueue.class));
+        mIconManager = new TintedIconManager(findViewById(R.id.statusIcons));
+        mIconManager.setBlockList(mBlockedIcons);
         Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager);
         onThemeChanged();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 4ef6668..415cfff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -1325,6 +1325,9 @@
         traceQsJank(true /* startTracing */, false /* wasCancelled */);
         flingSettings(0 /* velocity */, FLING_EXPAND);
         mQSDetailDisplayer.showDetailAdapter(qsDetailAdapter, 0, 0);
+        if (mAccessibilityManager.isEnabled()) {
+            mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
+        }
     }
 
     public void expandWithoutQs() {
@@ -1824,6 +1827,7 @@
             mNotificationContainerParent.setQsExpanded(expanded);
             mPulseExpansionHandler.setQsExpanded(expanded);
             mKeyguardBypassController.setQSExpanded(expanded);
+            mStatusBarKeyguardViewManager.setQsExpanded(expanded);
         }
     }
 
@@ -3389,7 +3393,7 @@
         return new TouchHandler() {
             @Override
             public boolean onInterceptTouchEvent(MotionEvent event) {
-                if (mStatusBarKeyguardViewManager.isShowingAlternativeAuthOrAnimating()) {
+                if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
                     return true;
                 }
                 if (mBlockTouches || mQsFullyExpanded && mQs.disallowPanelTouches()) {
@@ -3420,8 +3424,8 @@
             @Override
             public boolean onTouch(View v, MotionEvent event) {
                 final boolean showingOrAnimatingAltAuth =
-                        mStatusBarKeyguardViewManager.isShowingAlternativeAuthOrAnimating();
-                if (showingOrAnimatingAltAuth) {
+                        mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating();
+                if (showingOrAnimatingAltAuth && event.getAction() == MotionEvent.ACTION_DOWN) {
                     mStatusBarKeyguardViewManager.resetAlternateAuth();
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index ed4f324..77abe79 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -454,7 +454,7 @@
                 mView.postOnAnimation(mPostCollapseRunnable);
             }
         } else if (!mStatusBar.isBouncerShowing()
-                && !mStatusBarKeyguardViewManager.isShowingAlternativeAuthOrAnimating()) {
+                && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
             boolean expands = onEmptySpaceClick(mInitialTouchX);
             onTrackingStopped(expands);
         }
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 0e1fe22..c83b60d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -4224,7 +4224,7 @@
                 mNotificationPanelViewController.isLaunchingAffordanceWithPreview();
         mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview);
 
-        if (mStatusBarKeyguardViewManager.isShowingAlternativeAuth()) {
+        if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
             mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED);
         } else if (mBouncerShowing) {
             // Bouncer needs the front scrim when it's on top of an activity,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 8fe9a48..93b83d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -18,6 +18,7 @@
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.os.Bundle;
 import android.text.TextUtils;
@@ -37,7 +38,6 @@
 import com.android.systemui.demomode.DemoModeCommandReceiver;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusBarMobileView;
 import com.android.systemui.statusbar.StatusBarWifiView;
@@ -46,6 +46,7 @@
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 
+import java.util.ArrayList;
 import java.util.List;
 
 public interface StatusBarIconController {
@@ -54,15 +55,22 @@
      * When an icon is added with TAG_PRIMARY, it will be treated as the primary icon
      * in that slot and not added as a sub slot.
      */
-    public static final int TAG_PRIMARY = 0;
+    int TAG_PRIMARY = 0;
 
-    public void addIconGroup(IconManager iconManager);
-    public void removeIconGroup(IconManager iconManager);
-    public void setExternalIcon(String slot);
-    public void setIcon(String slot, int resourceId, CharSequence contentDescription);
-    public void setIcon(String slot, StatusBarIcon icon);
-    public void setSignalIcon(String slot, WifiIconState state);
-    public void setMobileIcons(String slot, List<MobileIconState> states);
+    /** */
+    void addIconGroup(IconManager iconManager);
+    /** */
+    void removeIconGroup(IconManager iconManager);
+    /** */
+    void setExternalIcon(String slot);
+    /** */
+    void setIcon(String slot, int resourceId, CharSequence contentDescription);
+    /** */
+    void setIcon(String slot, StatusBarIcon icon);
+    /** */
+    void setSignalIcon(String slot, WifiIconState state);
+    /** */
+    void setMobileIcons(String slot, List<MobileIconState> states);
     /**
      * Display the no calling & SMS icons.
      */
@@ -85,8 +93,9 @@
      * If you don't know what to pass for `tag`, either remove all icons for slot, or use
      * TAG_PRIMARY to refer to the first icon at a given slot.
      */
-    public void removeIcon(String slot, int tag);
-    public void removeAllIconsForSlot(String slot);
+    void removeIcon(String slot, int tag);
+    /** */
+    void removeAllIconsForSlot(String slot);
 
     // TODO: See if we can rename this tunable name.
     String ICON_HIDE_LIST = "icon_blacklist";
@@ -108,12 +117,12 @@
     /**
      * Version of ViewGroup that observes state from the DarkIconDispatcher.
      */
-    public static class DarkIconManager extends IconManager {
+    class DarkIconManager extends IconManager {
         private final DarkIconDispatcher mDarkIconDispatcher;
         private int mIconHPadding;
 
-        public DarkIconManager(LinearLayout linearLayout, CommandQueue commandQueue) {
-            super(linearLayout, commandQueue);
+        public DarkIconManager(LinearLayout linearLayout) {
+            super(linearLayout);
             mIconHPadding = mContext.getResources().getDimensionPixelSize(
                     R.dimen.status_bar_icon_padding);
             mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
@@ -169,11 +178,12 @@
         }
     }
 
-    public static class TintedIconManager extends IconManager {
+    /** */
+    class TintedIconManager extends IconManager {
         private int mColor;
 
-        public TintedIconManager(ViewGroup group, CommandQueue commandQueue) {
-            super(group, commandQueue);
+        public TintedIconManager(ViewGroup group) {
+            super(group);
         }
 
         @Override
@@ -219,7 +229,9 @@
         private boolean mIsInDemoMode;
         protected DemoStatusIcons mDemoStatusIcons;
 
-        public IconManager(ViewGroup group, CommandQueue commandQueue) {
+        protected ArrayList<String> mBlockList = new ArrayList<>();
+
+        public IconManager(ViewGroup group) {
             mGroup = group;
             mContext = group.getContext();
             mIconSize = mContext.getResources().getDimensionPixelSize(
@@ -234,6 +246,15 @@
             mDemoable = demoable;
         }
 
+        public void setBlockList(@Nullable List<String> blockList) {
+            mBlockList.clear();
+            if (blockList == null || blockList.isEmpty()) {
+                return;
+            }
+
+            mBlockList.addAll(blockList);
+        }
+
         public void setShouldLog(boolean should) {
             mShouldLog = should;
         }
@@ -249,6 +270,11 @@
 
         protected StatusIconDisplayable addHolder(int index, String slot, boolean blocked,
                 StatusBarIconHolder holder) {
+            // This is a little hacky, and probably regrettable, but just set `blocked` on any icon
+            // that is in our blocked list, then we'll never see it
+            if (mBlockList.contains(slot)) {
+                blocked = true;
+            }
             switch (holder.getType()) {
                 case TYPE_ICON:
                     return addIcon(index, slot, blocked, holder.getIcon());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 6404aea..75900a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -66,6 +66,7 @@
 
     private Context mContext;
 
+    /** */
     @Inject
     public StatusBarIconControllerImpl(
             Context context,
@@ -84,6 +85,7 @@
         demoModeController.addCallback(this);
     }
 
+    /** */
     @Override
     public void addIconGroup(IconManager group) {
         mIconGroups.add(group);
@@ -101,12 +103,14 @@
         }
     }
 
+    /** */
     @Override
     public void removeIconGroup(IconManager group) {
         group.destroy();
         mIconGroups.remove(group);
     }
 
+    /** */
     @Override
     public void onTuningChanged(String key, String newValue) {
         if (!ICON_HIDE_LIST.equals(key)) {
@@ -149,6 +153,7 @@
         mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, hidden, holder));
     }
 
+    /** */
     @Override
     public void setIcon(String slot, int resourceId, CharSequence contentDescription) {
         int index = getSlotIndex(slot);
@@ -290,8 +295,9 @@
      * For backwards compatibility, in the event that someone gives us a slot and a status bar icon
      */
     private void setIcon(int index, StatusBarIcon icon) {
+        String slot = getSlotName(index);
         if (icon == null) {
-            removeAllIconsForSlot(getSlotName(index));
+            removeAllIconsForSlot(slot);
             return;
         }
 
@@ -299,6 +305,7 @@
         setIcon(index, holder);
     }
 
+    /** */
     @Override
     public void setIcon(int index, @NonNull StatusBarIconHolder holder) {
         boolean isNew = getIcon(index, holder.getTag()) == null;
@@ -328,6 +335,7 @@
         handleSet(index, holder);
     }
 
+    /** */
     @Override
     public void setIconAccessibilityLiveRegion(String slotName, int accessibilityLiveRegion) {
         Slot slot = getSlot(slotName);
@@ -344,15 +352,18 @@
         }
     }
 
+    /** */
     public void removeIcon(String slot) {
         removeAllIconsForSlot(slot);
     }
 
+    /** */
     @Override
     public void removeIcon(String slot, int tag) {
         removeIcon(getSlotIndex(slot), tag);
     }
 
+    /** */
     @Override
     public void removeAllIconsForSlot(String slotName) {
         Slot slot = getSlot(slotName);
@@ -369,6 +380,7 @@
         }
     }
 
+    /** */
     @Override
     public void removeIcon(int index, int tag) {
         if (getIcon(index, tag) == null) {
@@ -384,6 +396,7 @@
         mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
     }
 
+    /** */
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println(TAG + " state:");
@@ -402,6 +415,7 @@
         super.dump(pw);
     }
 
+    /** */
     @Override
     public void onDemoModeStarted() {
         for (IconManager manager : mIconGroups) {
@@ -411,6 +425,7 @@
         }
     }
 
+    /** */
     @Override
     public void onDemoModeFinished() {
         for (IconManager manager : mIconGroups) {
@@ -420,6 +435,7 @@
         }
     }
 
+    /** */
     @Override
     public void dispatchDemoCommand(String command, Bundle args) {
         for (IconManager manager : mIconGroups) {
@@ -429,6 +445,7 @@
         }
     }
 
+    /** */
     @Override
     public List<String> demoCommands() {
         List<String> s = new ArrayList<>();
@@ -436,6 +453,7 @@
         return s;
     }
 
+    /** */
     @Override
     public void onDensityOrFontScaleChanged() {
         loadDimens();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index 19db02a..af342dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -39,7 +39,10 @@
     private MobileIconState mMobileState;
     private int mType = TYPE_ICON;
     private int mTag = 0;
-    private boolean mVisible = true;
+
+    private StatusBarIconHolder() {
+
+    }
 
     public static StatusBarIconHolder fromIcon(StatusBarIcon icon) {
         StatusBarIconHolder wrapper = new StatusBarIconHolder();
@@ -48,7 +51,10 @@
         return wrapper;
     }
 
-    public static StatusBarIconHolder fromResId(Context context, int resId,
+    /** */
+    public static StatusBarIconHolder fromResId(
+            Context context,
+            int resId,
             CharSequence contentDescription) {
         StatusBarIconHolder holder = new StatusBarIconHolder();
         holder.mIcon = new StatusBarIcon(UserHandle.SYSTEM, context.getPackageName(),
@@ -56,6 +62,7 @@
         return holder;
     }
 
+    /** */
     public static StatusBarIconHolder fromWifiIconState(WifiIconState state) {
         StatusBarIconHolder holder = new StatusBarIconHolder();
         holder.mWifiState = state;
@@ -63,6 +70,7 @@
         return holder;
     }
 
+    /** */
     public static StatusBarIconHolder fromMobileIconState(MobileIconState state) {
         StatusBarIconHolder holder = new StatusBarIconHolder();
         holder.mMobileState = state;
@@ -75,7 +83,8 @@
      * Creates a new StatusBarIconHolder from a CallIndicatorIconState.
      */
     public static StatusBarIconHolder fromCallIndicatorState(
-            Context context, CallIndicatorIconState state) {
+            Context context,
+            CallIndicatorIconState state) {
         StatusBarIconHolder holder = new StatusBarIconHolder();
         int resId = state.isNoCalling ? state.noCallingResId : state.callStrengthResId;
         String contentDescription = state.isNoCalling
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index c1f300b..7ee7aa4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -476,11 +476,12 @@
                 return;
             }
 
-            if (mAlternateAuthInterceptor != null
-                    && mAlternateAuthInterceptor.showAlternativeAuthMethod()) {
-                mStatusBar.updateScrimController();
+            if (mAlternateAuthInterceptor != null) {
                 mAfterKeyguardGoneAction = r;
                 mKeyguardGoneCancelAction = cancelAction;
+                if (mAlternateAuthInterceptor.showAlternativeAuthMethod()) {
+                    mStatusBar.updateScrimController();
+                }
                 return;
             }
 
@@ -528,7 +529,7 @@
      * Stop showing any alternate auth methods
      */
     public void resetAlternateAuth() {
-        if (mAlternateAuthInterceptor != null && mAlternateAuthInterceptor.reset()) {
+        if (mAlternateAuthInterceptor != null && mAlternateAuthInterceptor.resetForceShow()) {
             mStatusBar.updateScrimController();
         }
     }
@@ -1125,18 +1126,27 @@
         setDozing(isDozing);
     }
 
+    /**
+     * Set whether qs is currently expanded
+     */
+    public void setQsExpanded(boolean expanded) {
+        if (mAlternateAuthInterceptor != null) {
+            mAlternateAuthInterceptor.setQsExpanded(expanded);
+        }
+    }
+
     public KeyguardBouncer getBouncer() {
         return mBouncer;
     }
 
-    public boolean isShowingAlternativeAuth() {
+    public boolean isShowingAlternateAuth() {
         return mAlternateAuthInterceptor != null
-                && mAlternateAuthInterceptor.isShowingAlternativeAuth();
+                && mAlternateAuthInterceptor.isShowingAlternateAuth();
     }
 
-    public boolean isShowingAlternativeAuthOrAnimating() {
+    public boolean isShowingAlternateAuthOrAnimating() {
         return mAlternateAuthInterceptor != null
-                && (mAlternateAuthInterceptor.isShowingAlternativeAuth()
+                && (mAlternateAuthInterceptor.isShowingAlternateAuth()
                 || mAlternateAuthInterceptor.isAnimating());
     }
 
@@ -1169,12 +1179,12 @@
          * reset the state to the default (only keyguard showing, no auth methods showing)
          * @return whether alternative auth method was newly hidden
          */
-        boolean reset();
+        boolean resetForceShow();
 
         /**
          * @return true if alternative auth method is showing
          */
-        boolean isShowingAlternativeAuth();
+        boolean isShowingAlternateAuth();
 
         /**
          * print information for the alternate auth interceptor registered
@@ -1185,5 +1195,10 @@
          * @return true if the new auth method is currently animating in or out.
          */
         boolean isAnimating();
+
+        /**
+         * Set whether qs is currently expanded
+         */
+        void setQsExpanded(boolean expanded);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 525f220..94edd1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
@@ -187,7 +188,7 @@
                         boolean removedByUser,
                         int reason) {
                     StatusBarNotificationPresenter.this.onNotificationRemoved(
-                            entry.getKey(), entry.getSbn());
+                            entry.getKey(), entry.getSbn(), reason);
                     if (removedByUser) {
                         maybeEndAmbientPulse();
                     }
@@ -301,13 +302,14 @@
         mNotificationPanel.updateNotificationViews(reason);
     }
 
-    public void onNotificationRemoved(String key, StatusBarNotification old) {
+    private void onNotificationRemoved(String key, StatusBarNotification old, int reason) {
         if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
 
         if (old != null) {
             if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
                     && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) {
-                if (mStatusBarStateController.getState() == StatusBarState.SHADE) {
+                if (mStatusBarStateController.getState() == StatusBarState.SHADE
+                        && reason != NotificationListenerService.REASON_CLICK) {
                     mCommandQueue.animateCollapsePanels();
                 } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
                         && !isCollapsing()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
index 5dc9104..f2ee858 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
@@ -21,7 +21,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.net.ConnectivityManager;
 import android.provider.Settings;
 import android.telephony.SubscriptionInfo;
 import android.telephony.TelephonyManager;
@@ -95,8 +94,7 @@
     }
 
     public void update() {
-        boolean hasMobile = ConnectivityManager.from(mContext)
-                .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+        boolean hasMobile = mContext.getSystemService(TelephonyManager.class).isDataCapable();
         boolean airplaneMode = (Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.AIRPLANE_MODE_ON, 0) == 1);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index db039b4..8a86021 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -79,6 +79,7 @@
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collections;
 import java.util.Comparator;
@@ -241,8 +242,7 @@
         mSubscriptionManager = subManager;
         mSubDefaults = defaultsHandler;
         mConnectivityManager = connectivityManager;
-        mHasMobileDataFeature =
-                mConnectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+        mHasMobileDataFeature = telephonyManager.isDataCapable();
         mDemoModeController = demoModeController;
 
         // telephony
@@ -337,10 +337,19 @@
 
                 // This callback is invoked a lot (i.e. when RSSI changes), so avoid updating
                 // icons when connectivity state has remained the same.
-                if (network.equals(mLastNetwork) &&
-                    networkCapabilities.equalsTransportTypes(mLastNetworkCapabilities) &&
-                    validated == lastValidated) {
-                    return;
+                if (network.equals(mLastNetwork) && validated == lastValidated) {
+                    // Should not rely on getTransportTypes() returning the same order of transport
+                    // types. So sort the array before comparing.
+                    int[] newTypes = networkCapabilities.getTransportTypes();
+                    Arrays.sort(newTypes);
+
+                    int[] lastTypes = (mLastNetworkCapabilities != null)
+                            ? mLastNetworkCapabilities.getTransportTypes() : null;
+                    if (lastTypes != null) Arrays.sort(lastTypes);
+
+                    if (Arrays.equals(newTypes, lastTypes)) {
+                        return;
+                    }
                 }
                 mLastNetwork = network;
                 mLastNetworkCapabilities = networkCapabilities;
@@ -430,14 +439,13 @@
         filter.addAction(Intent.ACTION_SERVICE_STATE);
         filter.addAction(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED);
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
-        filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
         filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
         mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler);
         mListening = true;
 
         // Initial setup of connectivity. Handled as if we had received a sticky broadcast of
-        // ConnectivityManager.CONNECTIVITY_ACTION or ConnectivityManager.INET_CONDITION_ACTION.
+        // ConnectivityManager.CONNECTIVITY_ACTION.
         mReceiverHandler.post(this::updateConnectivity);
 
         // Initial setup of WifiSignalController. Handled as if we had received a sticky broadcast
@@ -682,7 +690,6 @@
         final String action = intent.getAction();
         switch (action) {
             case ConnectivityManager.CONNECTIVITY_ACTION:
-            case ConnectivityManager.INET_CONDITION_ACTION:
                 updateConnectivity();
                 break;
             case Intent.ACTION_AIRPLANE_MODE_CHANGED:
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index bbb2f1a..278663b 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -65,8 +65,6 @@
             "android.theme.customization.accent_color";
     static final String OVERLAY_CATEGORY_SYSTEM_PALETTE =
             "android.theme.customization.system_palette";
-    static final String OVERLAY_CATEGORY_NEUTRAL_PALETTE =
-            "android.theme.customization.neutral_palette";
     @VisibleForTesting
     static final String OVERLAY_CATEGORY_FONT = "android.theme.customization.font";
     @VisibleForTesting
@@ -94,7 +92,6 @@
      */
     static final List<String> THEME_CATEGORIES = Lists.newArrayList(
             OVERLAY_CATEGORY_SYSTEM_PALETTE,
-            OVERLAY_CATEGORY_NEUTRAL_PALETTE,
             OVERLAY_CATEGORY_ICON_LAUNCHER,
             OVERLAY_CATEGORY_SHAPE,
             OVERLAY_CATEGORY_FONT,
@@ -108,7 +105,6 @@
     @VisibleForTesting
     static final Set<String> SYSTEM_USER_CATEGORIES = Sets.newHashSet(
             OVERLAY_CATEGORY_SYSTEM_PALETTE,
-            OVERLAY_CATEGORY_NEUTRAL_PALETTE,
             OVERLAY_CATEGORY_ACCENT_COLOR,
             OVERLAY_CATEGORY_FONT,
             OVERLAY_CATEGORY_SHAPE,
@@ -131,8 +127,8 @@
         mLauncherPackage = launcherPackage;
         mThemePickerPackage = themePickerPackage;
         mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet(
-                OVERLAY_CATEGORY_SYSTEM_PALETTE, OVERLAY_CATEGORY_NEUTRAL_PALETTE,
-                OVERLAY_CATEGORY_ACCENT_COLOR, OVERLAY_CATEGORY_FONT, OVERLAY_CATEGORY_SHAPE,
+                OVERLAY_CATEGORY_SYSTEM_PALETTE, OVERLAY_CATEGORY_ACCENT_COLOR,
+                OVERLAY_CATEGORY_FONT, OVERLAY_CATEGORY_SHAPE,
                 OVERLAY_CATEGORY_ICON_ANDROID));
         mTargetPackageToCategories.put(SYSUI_PACKAGE,
                 Sets.newHashSet(OVERLAY_CATEGORY_ICON_SYSUI));
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index f192287..d317712 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -16,7 +16,6 @@
 package com.android.systemui.theme;
 
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
-import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_NEUTRAL_PALETTE;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
 
 import android.annotation.Nullable;
@@ -82,9 +81,8 @@
     protected static final String TAG = "ThemeOverlayController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    protected static final int PRIMARY = 0;
-    protected static final int SECONDARY = 1;
-    protected static final int NEUTRAL = 2;
+    protected static final int NEUTRAL = 0;
+    protected static final int ACCENT = 1;
 
     private final ThemeOverlayApplier mThemeManager;
     private final UserManager mUserManager;
@@ -103,8 +101,6 @@
     protected int mMainWallpaperColor = Color.TRANSPARENT;
     // Accent color extracted from wallpaper, NOT the color used on the overlay
     protected int mWallpaperAccentColor = Color.TRANSPARENT;
-    // System colors overlay
-    private FabricatedOverlay mPrimaryOverlay;
     // Accent colors overlay
     private FabricatedOverlay mSecondaryOverlay;
     // Neutral system colors overlay
@@ -205,7 +201,7 @@
             mainColor = Color.TRANSPARENT;
             accentCandidate = Color.TRANSPARENT;
         } else {
-            mainColor = getDominantColor(currentColors);
+            mainColor = getNeutralColor(currentColors);
             accentCandidate = getAccentColor(currentColors);
         }
 
@@ -218,13 +214,12 @@
         mWallpaperAccentColor = accentCandidate;
 
         if (mIsMonetEnabled) {
-            mPrimaryOverlay = getOverlay(mMainWallpaperColor, PRIMARY);
-            mSecondaryOverlay = getOverlay(mWallpaperAccentColor, SECONDARY);
+            mSecondaryOverlay = getOverlay(mWallpaperAccentColor, ACCENT);
             mNeutralOverlay = getOverlay(mMainWallpaperColor, NEUTRAL);
             mNeedsOverlayCreation = true;
             if (DEBUG) {
-                Log.d(TAG, "fetched overlays. primary: " + mPrimaryOverlay + " secondary: "
-                        + mSecondaryOverlay + " neutral: " + mNeutralOverlay);
+                Log.d(TAG, "fetched overlays. accent: " + mSecondaryOverlay
+                        + " neutral: " + mNeutralOverlay);
             }
         }
 
@@ -234,7 +229,7 @@
     /**
      * Return the main theme color from a given {@link WallpaperColors} instance.
      */
-    protected int getDominantColor(@NonNull WallpaperColors wallpaperColors) {
+    protected int getNeutralColor(@NonNull WallpaperColors wallpaperColors) {
         return wallpaperColors.getPrimaryColor().toArgb();
     }
 
@@ -283,8 +278,6 @@
         if (mIsMonetEnabled && systemPalette != null && systemPalette.getPackageName() != null) {
             try {
                 int color = Integer.parseInt(systemPalette.getPackageName().toLowerCase(), 16);
-                mPrimaryOverlay = getOverlay(color, PRIMARY);
-                // Neutral palette is always derived from primary color.
                 mNeutralOverlay = getOverlay(color, NEUTRAL);
                 mNeedsOverlayCreation = true;
                 categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE);
@@ -308,7 +301,7 @@
         if (mIsMonetEnabled && accentPalette != null && accentPalette.getPackageName() != null) {
             try {
                 int color = Integer.parseInt(accentPalette.getPackageName().toLowerCase(), 16);
-                mSecondaryOverlay = getOverlay(color, SECONDARY);
+                mSecondaryOverlay = getOverlay(color, ACCENT);
                 mNeedsOverlayCreation = true;
                 categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR);
             } catch (NumberFormatException e) {
@@ -326,9 +319,8 @@
         // Compatibility with legacy themes, where full packages were defined, instead of just
         // colors.
         if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE)
-                && mPrimaryOverlay != null) {
-            categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE, mPrimaryOverlay.getIdentifier());
-            categoryToPackage.put(OVERLAY_CATEGORY_NEUTRAL_PALETTE,
+                && mNeutralOverlay != null) {
+            categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE,
                     mNeutralOverlay.getIdentifier());
         }
         if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_ACCENT_COLOR)
@@ -350,7 +342,7 @@
         if (mNeedsOverlayCreation) {
             mNeedsOverlayCreation = false;
             mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] {
-                    mPrimaryOverlay, mSecondaryOverlay, mNeutralOverlay
+                    mSecondaryOverlay, mNeutralOverlay
             }, currentUser, managedProfiles);
         } else {
             mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, currentUser,
@@ -363,7 +355,6 @@
         pw.println("mSystemColors=" + mSystemColors);
         pw.println("mMainWallpaperColor=" + Integer.toHexString(mMainWallpaperColor));
         pw.println("mWallpaperAccentColor=" + Integer.toHexString(mWallpaperAccentColor));
-        pw.println("mPrimaryOverlay=" + mPrimaryOverlay);
         pw.println("mSecondaryOverlay=" + mSecondaryOverlay);
         pw.println("mNeutralOverlay=" + mNeutralOverlay);
         pw.println("mIsMonetEnabled=" + mIsMonetEnabled);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
index aa4122f..6f2c0af 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
@@ -37,7 +37,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.net.ConnectivityManager;
+import android.content.pm.PackageManager;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
@@ -99,7 +99,7 @@
     @Mock
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock
-    private ConnectivityManager mConnectivityManager;
+    private PackageManager mPackageManager;
     @Mock
     private TelephonyManager mTelephonyManager;
     @Mock
@@ -123,8 +123,8 @@
         mTestableLooper = TestableLooper.get(this);
 
         mContext.addMockSystemService(WifiManager.class, mWifiManager);
-        mContext.addMockSystemService(ConnectivityManager.class, mConnectivityManager);
-        when(mConnectivityManager.isNetworkSupported(anyInt())).thenReturn(true);
+        mContext.addMockSystemService(PackageManager.class, mPackageManager);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true);
         mContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
         mContext.addMockSystemService(SubscriptionManager.class, mSubscriptionManager);
         mContext.getOrCreateTestableResources().addOverride(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index 65f0f7b..4410992 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -170,11 +170,11 @@
 
         mAltAuthInterceptor.showAlternativeAuthMethod(); // force show
         assertFalse(mController.shouldPauseAuth());
-        assertTrue(mAltAuthInterceptor.isShowingAlternativeAuth());
+        assertTrue(mAltAuthInterceptor.isShowingAlternateAuth());
 
-        mAltAuthInterceptor.reset(); // stop force show
+        mAltAuthInterceptor.resetForceShow(); // stop force show
         assertTrue(mController.shouldPauseAuth());
-        assertFalse(mAltAuthInterceptor.isShowingAlternativeAuth());
+        assertFalse(mAltAuthInterceptor.isShowingAlternateAuth());
     }
 
     @Test
@@ -190,7 +190,7 @@
         mController.onViewDetached();
 
         // THEN alt auth state reports not showing
-        assertFalse(mAltAuthInterceptor.isShowingAlternativeAuth());
+        assertFalse(mAltAuthInterceptor.isShowingAlternateAuth());
     }
 
     private void sendStatusBarStateChanged(int statusBarState) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
index ee98a59..1c7a84a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
@@ -16,20 +16,11 @@
 
 package com.android.systemui.people;
 
-import static android.app.Notification.CATEGORY_MISSED_CALL;
-import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
-import static android.app.people.ConversationStatus.ACTIVITY_GAME;
-import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
-import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
-import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH;
-
 import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
-import static com.android.systemui.people.PeopleSpaceUtils.REQUIRED_WIDTH_FOR_MEDIUM;
 import static com.android.systemui.people.widget.AppWidgetOptionsHelper.OPTIONS_PEOPLE_TILE;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -48,7 +39,6 @@
 import android.app.NotificationManager;
 import android.app.Person;
 import android.app.people.ConversationChannel;
-import android.app.people.ConversationStatus;
 import android.app.people.IPeopleManager;
 import android.app.people.PeopleSpaceTile;
 import android.appwidget.AppWidgetManager;
@@ -70,9 +60,7 @@
 import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
-import android.view.View;
-import android.widget.RemoteViews;
-import android.widget.TextView;
+import android.util.DisplayMetrics;
 
 import androidx.test.filters.SmallTest;
 
@@ -117,8 +105,6 @@
     private static final int TEST_COLUMN_INDEX = 1;
     private static final Uri URI = Uri.parse("fake_uri");
     private static final Icon ICON = Icon.createWithResource("package", R.drawable.ic_android);
-    private static final String GAME_DESCRIPTION = "Playing a game!";
-    private static final CharSequence MISSED_CALL = "Custom missed call message";
     private static final String NAME = "username";
     private static final Person PERSON = new Person.Builder()
             .setName("name")
@@ -126,11 +112,6 @@
             .setUri(URI.toString())
             .setBot(false)
             .build();
-    private static final PeopleSpaceTile PERSON_TILE_WITHOUT_NOTIFICATION =
-            new PeopleSpaceTile
-                    .Builder(SHORTCUT_ID_1, NAME, ICON, new Intent())
-                    .setLastInteractionTimestamp(0L)
-                    .build();
     private static final PeopleSpaceTile PERSON_TILE =
             new PeopleSpaceTile
                     .Builder(SHORTCUT_ID_1, NAME, ICON, new Intent())
@@ -139,16 +120,6 @@
                     .setNotificationContent(NOTIFICATION_CONTENT)
                     .setNotificationDataUri(URI)
                     .build();
-    private static final ConversationStatus GAME_STATUS =
-            new ConversationStatus
-                    .Builder(PERSON_TILE.getId(), ACTIVITY_GAME)
-                    .setDescription(GAME_DESCRIPTION)
-                    .build();
-    private static final ConversationStatus NEW_STORY_WITH_AVAILABILITY =
-            new ConversationStatus
-                    .Builder(PERSON_TILE.getId(), ACTIVITY_NEW_STORY)
-                    .setAvailability(AVAILABILITY_AVAILABLE)
-                    .build();
 
     private final ShortcutInfo mShortcutInfo = new ShortcutInfo.Builder(mContext,
             SHORTCUT_ID_1).setLongLabel(
@@ -244,6 +215,12 @@
         when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITHOUT_SHORTCUT)))
                 .thenReturn(new Bundle());
 
+        Configuration configuration = mock(Configuration.class);
+        DisplayMetrics displayMetrics = mock(DisplayMetrics.class);
+        Resources resources = mock(Resources.class);
+        when(mMockContext.getResources()).thenReturn(resources);
+        when(resources.getConfiguration()).thenReturn(configuration);
+        when(resources.getDisplayMetrics()).thenReturn(displayMetrics);
         when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
         when(mMockContentResolver.query(any(Uri.class), any(), anyString(), any(),
                 isNull())).thenReturn(mMockCursor);
@@ -254,10 +231,6 @@
         when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
         when(mMockContext.getString(R.string.over_timestamp)).thenReturn(
                 mContext.getString(R.string.over_timestamp));
-        Configuration configuration = mock(Configuration.class);
-        Resources resources = mock(Resources.class);
-        when(mMockContext.getResources()).thenReturn(resources);
-        when(resources.getConfiguration()).thenReturn(configuration);
         when(mPackageManager.getApplicationIcon(anyString())).thenReturn(null);
         when(mNotificationEntryManager.getVisibleNotifications())
                 .thenReturn(List.of(mNotificationEntry1, mNotificationEntry2, mNotificationEntry3));
@@ -362,95 +335,6 @@
     }
 
     @Test
-    public void testGetBackgroundTextFromMessageNoPunctuation() {
-        String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage("test");
-
-        assertThat(backgroundText).isNull();
-    }
-
-    @Test
-    public void testGetBackgroundTextFromMessageSingleExclamation() {
-        String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage("test!");
-
-        assertThat(backgroundText).isNull();
-    }
-
-    @Test
-    public void testGetBackgroundTextFromMessageSingleQuestion() {
-        String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage("?test");
-
-        assertThat(backgroundText).isNull();
-    }
-
-    @Test
-    public void testGetBackgroundTextFromMessageSeparatedMarks() {
-        String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage("test! right!");
-
-        assertThat(backgroundText).isNull();
-    }
-
-    @Test
-    public void testGetBackgroundTextFromMessageDoubleExclamation() {
-        String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage("!!test");
-
-        assertThat(backgroundText).isEqualTo("!");
-    }
-
-    @Test
-    public void testGetBackgroundTextFromMessageDoubleQuestion() {
-        String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage("test??");
-
-        assertThat(backgroundText).isEqualTo("?");
-    }
-
-    @Test
-    public void testGetBackgroundTextFromMessageMixed() {
-        String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage("test?!");
-
-        assertThat(backgroundText).isEqualTo("!?");
-    }
-
-    @Test
-    public void testGetBackgroundTextFromMessageMixedInTheMiddle() {
-        String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage(
-                "test!? in the middle");
-
-        assertThat(backgroundText).isEqualTo("!?");
-    }
-
-    @Test
-    public void testGetBackgroundTextFromMessageMixedDifferentOrder() {
-        String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage(
-                "test!? in the middle");
-
-        assertThat(backgroundText).isEqualTo("!?");
-    }
-
-    @Test
-    public void testGetBackgroundTextFromMessageMultiple() {
-        String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage(
-                "test!?!!? in the middle");
-
-        assertThat(backgroundText).isEqualTo("!?");
-    }
-
-    @Test
-    public void testGetBackgroundTextFromMessageQuestionFirst() {
-        String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage(
-                "test?? in the middle!!");
-
-        assertThat(backgroundText).isEqualTo("?");
-    }
-
-    @Test
-    public void testGetBackgroundTextFromMessageExclamationFirst() {
-        String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage(
-                "test!! in the middle??");
-
-        assertThat(backgroundText).isEqualTo("!");
-    }
-
-    @Test
     public void testGetLastMessagingStyleMessage() {
         StatusBarNotification sbn = new SbnBuilder()
                 .setNotification(mNotification1)
@@ -687,225 +571,6 @@
                 any());
     }
 
-    @Test
-    public void testCreateRemoteViewsWithLastInteractionTime() {
-        RemoteViews views = PeopleSpaceUtils.createRemoteViews(mMockContext,
-                PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions);
-        View result = views.apply(mContext, null);
-
-        TextView name = (TextView) result.findViewById(R.id.name);
-        assertEquals(name.getText(), NAME);
-        // Has last interaction.
-        TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
-        assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
-        // No availability.
-        View availability = result.findViewById(R.id.availability);
-        assertEquals(View.GONE, availability.getVisibility());
-        // Shows person icon.
-        View personIcon = result.findViewById(R.id.person_icon);
-        assertEquals(View.VISIBLE, personIcon.getVisibility());
-        // No status.
-        assertThat((View) result.findViewById(R.id.text_content)).isNull();
-
-        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
-        RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
-                PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions);
-        View smallResult = smallView.apply(mContext, null);
-
-        // Show name over predefined icon.
-        assertEquals(View.VISIBLE, smallResult.findViewById(R.id.name).getVisibility());
-        assertEquals(View.GONE, smallResult.findViewById(R.id.predefined_icon).getVisibility());
-        // Shows person icon.
-        assertEquals(View.VISIBLE, smallResult.findViewById(R.id.person_icon).getVisibility());
-    }
-
-    @Test
-    public void testCreateRemoteViewsWithGameTypeOnlyIsIgnored() {
-        PeopleSpaceTile tileWithAvailabilityAndNewStory =
-                PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
-                        Arrays.asList(NEW_STORY_WITH_AVAILABILITY,
-                                new ConversationStatus.Builder(
-                                        PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
-                                        ACTIVITY_GAME).build())).build();
-        RemoteViews views = PeopleSpaceUtils.createRemoteViews(mMockContext,
-                tileWithAvailabilityAndNewStory, 0, mOptions);
-        View result = views.apply(mContext, null);
-
-        TextView name = (TextView) result.findViewById(R.id.name);
-        assertEquals(name.getText(), NAME);
-        // Has last interaction over status.
-        TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
-        assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
-        // Has availability.
-        View availability = result.findViewById(R.id.availability);
-        assertEquals(View.VISIBLE, availability.getVisibility());
-        // Has person icon.
-        View personIcon = result.findViewById(R.id.person_icon);
-        assertEquals(View.VISIBLE, personIcon.getVisibility());
-        // No status.
-        assertThat((View) result.findViewById(R.id.text_content)).isNull();
-
-        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
-        RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
-                tileWithAvailabilityAndNewStory, 0, mOptions);
-        View smallResult = smallView.apply(mContext, null);
-
-        // Show name rather than game type.
-        assertEquals(View.VISIBLE, smallResult.findViewById(R.id.name).getVisibility());
-        assertEquals(View.GONE, smallResult.findViewById(R.id.predefined_icon).getVisibility());
-        // Has person icon.
-        assertEquals(View.VISIBLE,
-                smallResult.findViewById(R.id.person_icon).getVisibility());
-    }
-
-    @Test
-    public void testCreateRemoteViewsWithBirthdayTypeOnlyIsNotIgnored() {
-        PeopleSpaceTile tileWithStatusTemplate =
-                PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
-                        Arrays.asList(
-                                NEW_STORY_WITH_AVAILABILITY, new ConversationStatus.Builder(
-                                        PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
-                                        ACTIVITY_BIRTHDAY).build())).build();
-        RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
-                tileWithStatusTemplate, 0, mOptions);
-        View result = views.apply(mContext, null);
-
-        TextView name = (TextView) result.findViewById(R.id.name);
-        assertEquals(name.getText(), NAME);
-        // Has availability.
-        View availability = result.findViewById(R.id.availability);
-        assertEquals(View.VISIBLE, availability.getVisibility());
-        // Has person icon.
-        View personIcon = result.findViewById(R.id.person_icon);
-        assertEquals(View.VISIBLE, personIcon.getVisibility());
-        // Has status text from backup text.
-        TextView statusContent = (TextView) result.findViewById(R.id.text_content);
-        assertEquals(statusContent.getText(), mContext.getString(R.string.birthday_status));
-
-        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
-        RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
-                tileWithStatusTemplate, 0, mOptions);
-        View smallResult = smallView.apply(mContext, null);
-
-        // Show icon instead of name.
-        assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
-        assertEquals(View.VISIBLE,
-                smallResult.findViewById(R.id.predefined_icon).getVisibility());
-        // Has person icon.
-        assertEquals(View.VISIBLE,
-                smallResult.findViewById(R.id.person_icon).getVisibility());
-    }
-
-    @Test
-    public void testCreateRemoteViewsWithStatusTemplate() {
-        PeopleSpaceTile tileWithStatusTemplate =
-                PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
-                        Arrays.asList(GAME_STATUS,
-                                NEW_STORY_WITH_AVAILABILITY)).build();
-        RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
-                tileWithStatusTemplate, 0, mOptions);
-        View result = views.apply(mContext, null);
-
-        TextView name = (TextView) result.findViewById(R.id.name);
-        assertEquals(name.getText(), NAME);
-        // Has availability.
-        View availability = result.findViewById(R.id.availability);
-        assertEquals(View.VISIBLE, availability.getVisibility());
-        // Has person icon.
-        View personIcon = result.findViewById(R.id.person_icon);
-        assertEquals(View.VISIBLE, personIcon.getVisibility());
-        // Has status.
-        TextView statusContent = (TextView) result.findViewById(R.id.text_content);
-        assertEquals(statusContent.getText(), GAME_DESCRIPTION);
-
-        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
-        RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
-                tileWithStatusTemplate, 0, mOptions);
-        View smallResult = smallView.apply(mContext, null);
-
-        // Show icon instead of name.
-        assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
-        assertEquals(View.VISIBLE,
-                smallResult.findViewById(R.id.predefined_icon).getVisibility());
-        // Has person icon.
-        assertEquals(View.VISIBLE,
-                smallResult.findViewById(R.id.person_icon).getVisibility());
-    }
-
-    @Test
-    public void testCreateRemoteViewsWithMissedCallNotification() {
-        PeopleSpaceTile tileWithMissedCallNotification = PERSON_TILE.toBuilder()
-                .setNotificationDataUri(null)
-                .setNotificationCategory(CATEGORY_MISSED_CALL)
-                .setNotificationContent(MISSED_CALL)
-                .build();
-        RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
-                tileWithMissedCallNotification, 0, mOptions);
-        View result = views.apply(mContext, null);
-
-        TextView name = (TextView) result.findViewById(R.id.name);
-        assertEquals(name.getText(), NAME);
-        // Has availability.
-        View availability = result.findViewById(R.id.availability);
-        assertEquals(View.GONE, availability.getVisibility());
-        // Has person icon.
-        View personIcon = result.findViewById(R.id.person_icon);
-        assertEquals(View.VISIBLE, personIcon.getVisibility());
-        // Has missed call notification content.
-        TextView statusContent = (TextView) result.findViewById(R.id.text_content);
-        assertEquals(statusContent.getText(), MISSED_CALL);
-
-        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
-        RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
-                tileWithMissedCallNotification, 0, mOptions);
-        View smallResult = smallView.apply(mContext, null);
-
-        // Show icon instead of name.
-        assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
-        assertEquals(View.VISIBLE,
-                smallResult.findViewById(R.id.predefined_icon).getVisibility());
-        // Has person icon.
-        assertEquals(View.VISIBLE, smallResult.findViewById(R.id.person_icon).getVisibility());
-    }
-
-    @Test
-    public void testCreateRemoteViewsWithNotificationTemplate() {
-        PeopleSpaceTile tileWithStatusAndNotification = PERSON_TILE.toBuilder()
-                .setNotificationDataUri(null)
-                .setStatuses(Arrays.asList(GAME_STATUS,
-                        NEW_STORY_WITH_AVAILABILITY)).build();
-        RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
-                tileWithStatusAndNotification, 0, mOptions);
-        View result = views.apply(mContext, null);
-
-        TextView name = (TextView) result.findViewById(R.id.name);
-        assertEquals(name.getText(), NAME);
-        TextView subtext = (TextView) result.findViewById(R.id.subtext);
-        assertEquals(View.GONE, subtext.getVisibility());
-        // Has availability.
-        View availability = result.findViewById(R.id.availability);
-        assertEquals(View.VISIBLE, availability.getVisibility());
-        // Has person icon.
-        View personIcon = result.findViewById(R.id.person_icon);
-        assertEquals(View.VISIBLE, personIcon.getVisibility());
-        // Has notification content.
-        TextView statusContent = (TextView) result.findViewById(R.id.text_content);
-        assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
-
-        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
-        RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
-                tileWithStatusAndNotification, 0, mOptions);
-        View smallResult = smallView.apply(mContext, null);
-
-        // Show icon instead of name.
-        assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
-        assertEquals(View.VISIBLE,
-                smallResult.findViewById(R.id.predefined_icon).getVisibility());
-        // Has person icon.
-        assertEquals(View.VISIBLE,
-                smallResult.findViewById(R.id.person_icon).getVisibility());
-    }
-
     private ConversationChannelWrapper getConversationChannelWrapper(String shortcutId,
             boolean importantConversation, long lastInteractionTimestamp) throws Exception {
         ConversationChannelWrapper convo = new ConversationChannelWrapper();
@@ -931,13 +596,4 @@
                 eq(shortcutId))).thenReturn(lastInteractionTimestamp);
         return convo;
     }
-
-    private ConversationChannel getConversationChannelWithoutTimestamp(String shortcutId)
-            throws Exception {
-        ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext, shortcutId).setLongLabel(
-                "name").build();
-        ConversationChannel convo = new ConversationChannel(shortcutInfo, 0, null, null,
-                0L, false);
-        return convo;
-    }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
new file mode 100644
index 0000000..39bf060
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
@@ -0,0 +1,587 @@
+/*
+ * 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.people;
+
+import static android.app.Notification.CATEGORY_MISSED_CALL;
+import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
+import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
+import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
+import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH;
+
+import static com.android.systemui.people.widget.AppWidgetOptionsHelper.OPTIONS_PEOPLE_TILE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.people.ConversationStatus;
+import android.app.people.PeopleSpaceTile;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Bundle;
+import android.testing.AndroidTestingRunner;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class PeopleTileViewHelperTest extends SysuiTestCase {
+
+    private static final String SHORTCUT_ID_1 = "101";
+    private static final String NOTIFICATION_KEY = "notification_key";
+    private static final String NOTIFICATION_CONTENT = "notification_content";
+    private static final Uri URI = Uri.parse("fake_uri");
+    private static final Icon ICON = Icon.createWithResource("package", R.drawable.ic_android);
+    private static final String GAME_DESCRIPTION = "Playing a game!";
+    private static final CharSequence MISSED_CALL = "Custom missed call message";
+    private static final String NAME = "username";
+    private static final PeopleSpaceTile PERSON_TILE_WITHOUT_NOTIFICATION =
+            new PeopleSpaceTile
+                    .Builder(SHORTCUT_ID_1, NAME, ICON, new Intent())
+                    .setLastInteractionTimestamp(0L)
+                    .build();
+    private static final PeopleSpaceTile PERSON_TILE =
+            new PeopleSpaceTile
+                    .Builder(SHORTCUT_ID_1, NAME, ICON, new Intent())
+                    .setLastInteractionTimestamp(123L)
+                    .setNotificationKey(NOTIFICATION_KEY)
+                    .setNotificationContent(NOTIFICATION_CONTENT)
+                    .setNotificationDataUri(URI)
+                    .build();
+    private static final ConversationStatus GAME_STATUS =
+            new ConversationStatus
+                    .Builder(PERSON_TILE.getId(), ACTIVITY_GAME)
+                    .setDescription(GAME_DESCRIPTION)
+                    .build();
+    private static final ConversationStatus NEW_STORY_WITH_AVAILABILITY =
+            new ConversationStatus
+                    .Builder(PERSON_TILE.getId(), ACTIVITY_NEW_STORY)
+                    .setAvailability(AVAILABILITY_AVAILABLE)
+                    .build();
+
+    @Mock
+    private Context mMockContext;
+    @Mock
+    private PackageManager mPackageManager;
+
+    private Bundle mOptions;
+    private PeopleTileViewHelper mPeopleTileViewHelper;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mOptions = new Bundle();
+        mOptions.putParcelable(OPTIONS_PEOPLE_TILE, PERSON_TILE);
+
+        when(mMockContext.getString(R.string.birthday_status)).thenReturn(
+                mContext.getString(R.string.birthday_status));
+        when(mMockContext.getString(R.string.basic_status)).thenReturn(
+                mContext.getString(R.string.basic_status));
+        when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mMockContext.getString(R.string.over_timestamp)).thenReturn(
+                mContext.getString(R.string.over_timestamp));
+        Configuration configuration = mock(Configuration.class);
+        DisplayMetrics displayMetrics = mock(DisplayMetrics.class);
+        Resources resources = mock(Resources.class);
+        when(mMockContext.getResources()).thenReturn(resources);
+        when(resources.getConfiguration()).thenReturn(configuration);
+        when(resources.getDisplayMetrics()).thenReturn(displayMetrics);
+        TextView textView = mock(TextView.class);
+        // when(new TextView(mMockContext)).thenReturn(textView);
+        when(textView.getLineHeight()).thenReturn(16);
+        when(mPackageManager.getApplicationIcon(anyString())).thenReturn(null);
+        mPeopleTileViewHelper = new PeopleTileViewHelper(mContext,
+                PERSON_TILE, 0, mOptions);
+    }
+
+    @Test
+    public void testCreateRemoteViewsWithLastInteractionTime() {
+        RemoteViews views = new PeopleTileViewHelper(mContext,
+                PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews();
+        View result = views.apply(mContext, null);
+
+        TextView name = (TextView) result.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        // Has last interaction.
+        TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
+        assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
+        // No availability.
+        assertEquals(View.GONE, result.findViewById(R.id.availability).getVisibility());
+        // Shows person icon.
+        assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+        // No status.
+        assertThat((View) result.findViewById(R.id.text_content)).isNull();
+
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_width_for_medium) - 1);
+        RemoteViews smallView = new PeopleTileViewHelper(mContext,
+                PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews();
+        View smallResult = smallView.apply(mContext, null);
+
+        // Show name over predefined icon.
+        assertEquals(View.VISIBLE, smallResult.findViewById(R.id.name).getVisibility());
+        assertEquals(View.GONE, smallResult.findViewById(R.id.predefined_icon).getVisibility());
+        // Shows person icon.
+        assertEquals(View.VISIBLE, smallResult.findViewById(R.id.person_icon).getVisibility());
+
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_width_for_large));
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_height_for_large));
+        RemoteViews largeView = new PeopleTileViewHelper(mContext,
+                PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews();
+        View largeResult = largeView.apply(mContext, null);
+
+        name = (TextView) largeResult.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        // Has last interaction.
+        lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
+        assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
+        // No availability.
+        assertEquals(View.GONE, result.findViewById(R.id.availability).getVisibility());
+        // Shows person icon.
+        assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+        // No status.
+        assertThat((View) result.findViewById(R.id.text_content)).isNull();
+    }
+
+    @Test
+    public void testCreateRemoteViewsWithGameTypeOnlyIsIgnored() {
+        PeopleSpaceTile tileWithAvailabilityAndNewStory =
+                PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
+                        Arrays.asList(NEW_STORY_WITH_AVAILABILITY,
+                                new ConversationStatus.Builder(
+                                        PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
+                                        ACTIVITY_GAME).build())).build();
+        RemoteViews views = new PeopleTileViewHelper(mContext,
+                tileWithAvailabilityAndNewStory, 0, mOptions).getViews();
+        View result = views.apply(mContext, null);
+
+        TextView name = (TextView) result.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        // Has last interaction over status.
+        TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
+        assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
+        // Has availability.
+        assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility());
+        // Has person icon.
+        assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+        // No status.
+        assertThat((View) result.findViewById(R.id.text_content)).isNull();
+
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_width_for_medium) - 1);
+        RemoteViews smallView = new PeopleTileViewHelper(mContext,
+                tileWithAvailabilityAndNewStory, 0, mOptions).getViews();
+        View smallResult = smallView.apply(mContext, null);
+
+        // Show name rather than game type.
+        assertEquals(View.VISIBLE, smallResult.findViewById(R.id.name).getVisibility());
+        assertEquals(View.GONE, smallResult.findViewById(R.id.predefined_icon).getVisibility());
+        // Has person icon.
+        assertEquals(View.VISIBLE,
+                smallResult.findViewById(R.id.person_icon).getVisibility());
+
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_width_for_large));
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_height_for_large));
+        RemoteViews largeView = new PeopleTileViewHelper(mContext,
+                tileWithAvailabilityAndNewStory, 0, mOptions).getViews();
+        View largeResult = largeView.apply(mContext, null);
+
+        name = (TextView) largeResult.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        // Has last interaction.
+        lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
+        assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
+        // Has availability.
+        assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility());
+        // Shows person icon.
+        assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+        // No status.
+        assertThat((View) result.findViewById(R.id.text_content)).isNull();
+    }
+
+    @Test
+    public void testCreateRemoteViewsWithBirthdayTypeOnlyIsNotIgnored() {
+        PeopleSpaceTile tileWithStatusTemplate =
+                PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
+                        Arrays.asList(
+                                NEW_STORY_WITH_AVAILABILITY, new ConversationStatus.Builder(
+                                        PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
+                                        ACTIVITY_BIRTHDAY).build())).build();
+        RemoteViews views = new PeopleTileViewHelper(mContext,
+                tileWithStatusTemplate, 0, mOptions).getViews();
+        View result = views.apply(mContext, null);
+
+        TextView name = (TextView) result.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        assertEquals(View.GONE, result.findViewById(R.id.subtext).getVisibility());
+        assertEquals(View.VISIBLE, result.findViewById(R.id.predefined_icon).getVisibility());
+        // Has availability.
+        assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility());
+        // Has person icon.
+        assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+        // Has status text from backup text.
+        TextView statusContent = (TextView) result.findViewById(R.id.text_content);
+        assertEquals(View.VISIBLE, statusContent.getVisibility());
+        assertEquals(statusContent.getText(), mContext.getString(R.string.birthday_status));
+        assertThat(statusContent.getMaxLines()).isEqualTo(3);
+
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_width_for_medium) - 1);
+        RemoteViews smallView = new PeopleTileViewHelper(mContext,
+                tileWithStatusTemplate, 0, mOptions).getViews();
+        View smallResult = smallView.apply(mContext, null);
+
+        // Show icon instead of name.
+        assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
+        assertEquals(View.VISIBLE,
+                smallResult.findViewById(R.id.predefined_icon).getVisibility());
+        // Has person icon.
+        assertEquals(View.VISIBLE,
+                smallResult.findViewById(R.id.person_icon).getVisibility());
+
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_width_for_large));
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_height_for_large));
+        RemoteViews largeView = new PeopleTileViewHelper(mContext,
+                tileWithStatusTemplate, 0, mOptions).getViews();
+        View largeResult = largeView.apply(mContext, null);
+
+        name = (TextView) largeResult.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        assertEquals(View.GONE, largeResult.findViewById(R.id.subtext).getVisibility());
+        assertEquals(View.VISIBLE, largeResult.findViewById(R.id.predefined_icon).getVisibility());
+        // Has availability.
+        assertEquals(View.VISIBLE, largeResult.findViewById(R.id.availability).getVisibility());
+        // Has person icon.
+        View personIcon = largeResult.findViewById(R.id.person_icon);
+        assertEquals(View.VISIBLE, personIcon.getVisibility());
+        // Has notification content.
+        statusContent = (TextView) largeResult.findViewById(R.id.text_content);
+        assertEquals(View.VISIBLE, statusContent.getVisibility());
+        assertEquals(statusContent.getText(), mContext.getString(R.string.birthday_status));
+        assertThat(statusContent.getMaxLines()).isEqualTo(3);
+    }
+
+    @Test
+    public void testCreateRemoteViewsWithStatusTemplate() {
+        PeopleSpaceTile tileWithStatusTemplate =
+                PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
+                        Arrays.asList(GAME_STATUS,
+                                NEW_STORY_WITH_AVAILABILITY)).build();
+        RemoteViews views = new PeopleTileViewHelper(mContext,
+                tileWithStatusTemplate, 0, mOptions).getViews();
+        View result = views.apply(mContext, null);
+
+        TextView name = (TextView) result.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        assertEquals(View.GONE, result.findViewById(R.id.subtext).getVisibility());
+        assertEquals(View.VISIBLE, result.findViewById(R.id.predefined_icon).getVisibility());
+        // Has availability.
+        assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility());
+        // Has person icon.
+        assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+        // Has status.
+        TextView statusContent = (TextView) result.findViewById(R.id.text_content);
+        assertEquals(statusContent.getText(), GAME_DESCRIPTION);
+        assertThat(statusContent.getMaxLines()).isEqualTo(3);
+
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_width_for_medium) - 1);
+        RemoteViews smallView = new PeopleTileViewHelper(mContext,
+                tileWithStatusTemplate, 0, mOptions).getViews();
+        View smallResult = smallView.apply(mContext, null);
+
+        // Show icon instead of name.
+        assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
+        assertEquals(View.VISIBLE,
+                smallResult.findViewById(R.id.predefined_icon).getVisibility());
+        // Has person icon.
+        assertEquals(View.VISIBLE,
+                smallResult.findViewById(R.id.person_icon).getVisibility());
+
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_width_for_large));
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_height_for_large));
+        RemoteViews largeView = new PeopleTileViewHelper(mContext,
+                tileWithStatusTemplate, 0, mOptions).getViews();
+        View largeResult = largeView.apply(mContext, null);
+
+        name = (TextView) largeResult.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        assertEquals(View.GONE, largeResult.findViewById(R.id.subtext).getVisibility());
+        assertEquals(View.VISIBLE, largeResult.findViewById(R.id.predefined_icon).getVisibility());
+        // Has availability.
+        assertEquals(View.VISIBLE, largeResult.findViewById(R.id.availability).getVisibility());
+        // Has person icon.
+        View personIcon = largeResult.findViewById(R.id.person_icon);
+        assertEquals(View.VISIBLE, personIcon.getVisibility());
+        // Has notification content.
+        statusContent = (TextView) largeResult.findViewById(R.id.text_content);
+        assertEquals(View.VISIBLE, statusContent.getVisibility());
+        assertEquals(statusContent.getText(), GAME_DESCRIPTION);
+        assertThat(statusContent.getMaxLines()).isEqualTo(3);
+    }
+
+    @Test
+    public void testCreateRemoteViewsWithMissedCallNotification() {
+        PeopleSpaceTile tileWithMissedCallNotification = PERSON_TILE.toBuilder()
+                .setNotificationDataUri(null)
+                .setNotificationCategory(CATEGORY_MISSED_CALL)
+                .setNotificationContent(MISSED_CALL)
+                .build();
+        RemoteViews views = new PeopleTileViewHelper(mContext,
+                tileWithMissedCallNotification, 0, mOptions).getViews();
+        View result = views.apply(mContext, null);
+
+        TextView name = (TextView) result.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        assertEquals(View.GONE, result.findViewById(R.id.subtext).getVisibility());
+        assertEquals(View.VISIBLE, result.findViewById(R.id.predefined_icon).getVisibility());
+        // Has availability.
+        assertEquals(View.GONE, result.findViewById(R.id.availability).getVisibility());
+        // Has person icon.
+        assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+        // Has missed call notification content.
+        TextView statusContent = (TextView) result.findViewById(R.id.text_content);
+        assertEquals(View.VISIBLE, statusContent.getVisibility());
+        assertEquals(statusContent.getText(), MISSED_CALL);
+        assertThat(statusContent.getMaxLines()).isEqualTo(3);
+
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_width_for_medium) - 1);
+        RemoteViews smallView = new PeopleTileViewHelper(mContext,
+                tileWithMissedCallNotification, 0, mOptions).getViews();
+        View smallResult = smallView.apply(mContext, null);
+
+        // Show icon instead of name.
+        assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
+        assertEquals(View.VISIBLE,
+                smallResult.findViewById(R.id.predefined_icon).getVisibility());
+        // Has person icon.
+        assertEquals(View.VISIBLE, smallResult.findViewById(R.id.person_icon).getVisibility());
+
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_width_for_large));
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_height_for_large));
+        RemoteViews largeView = new PeopleTileViewHelper(mContext,
+                tileWithMissedCallNotification, 0, mOptions).getViews();
+        View largeResult = largeView.apply(mContext, null);
+
+        name = (TextView) largeResult.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        assertEquals(View.GONE, largeResult.findViewById(R.id.subtext).getVisibility());
+        assertEquals(View.VISIBLE, largeResult.findViewById(R.id.predefined_icon).getVisibility());
+        // Has availability.
+        assertEquals(View.GONE, largeResult.findViewById(R.id.availability).getVisibility());
+        // Has person icon.
+        View personIcon = largeResult.findViewById(R.id.person_icon);
+        assertEquals(View.VISIBLE, personIcon.getVisibility());
+        // Has notification content.
+        statusContent = (TextView) largeResult.findViewById(R.id.text_content);
+        assertEquals(View.VISIBLE, statusContent.getVisibility());
+        assertEquals(statusContent.getText(), MISSED_CALL);
+        assertThat(statusContent.getMaxLines()).isEqualTo(3);
+    }
+
+    @Test
+    public void testCreateRemoteViewsWithNotificationTemplate() {
+        PeopleSpaceTile tileWithStatusAndNotification = PERSON_TILE.toBuilder()
+                .setNotificationDataUri(null)
+                .setStatuses(Arrays.asList(GAME_STATUS,
+                        NEW_STORY_WITH_AVAILABILITY)).build();
+        RemoteViews views = new PeopleTileViewHelper(mContext,
+                tileWithStatusAndNotification, 0, mOptions).getViews();
+        View result = views.apply(mContext, null);
+
+        TextView name = (TextView) result.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        assertEquals(View.GONE, result.findViewById(R.id.subtext).getVisibility());
+        assertEquals(View.GONE, result.findViewById(R.id.predefined_icon).getVisibility());
+        // Has availability.
+        assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility());
+        // Has person icon.
+        assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+        // Has notification content.
+        TextView statusContent = (TextView) result.findViewById(R.id.text_content);
+        assertEquals(View.VISIBLE, statusContent.getVisibility());
+        assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
+        assertThat(statusContent.getMaxLines()).isEqualTo(3);
+
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_width_for_medium) - 1);
+        RemoteViews smallView = new PeopleTileViewHelper(mContext,
+                tileWithStatusAndNotification, 0, mOptions).getViews();
+        View smallResult = smallView.apply(mContext, null);
+
+        // Show icon instead of name.
+        assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
+        assertEquals(View.VISIBLE,
+                smallResult.findViewById(R.id.predefined_icon).getVisibility());
+        // Has person icon.
+        assertEquals(View.VISIBLE,
+                smallResult.findViewById(R.id.person_icon).getVisibility());
+
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_width_for_large));
+        mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+                getSizeInDp(R.dimen.required_height_for_large));
+        RemoteViews largeView = new PeopleTileViewHelper(mContext,
+                tileWithStatusAndNotification, 0, mOptions).getViews();
+        View largeResult = largeView.apply(mContext, null);
+
+        name = (TextView) largeResult.findViewById(R.id.name);
+        assertEquals(name.getText(), NAME);
+        assertEquals(View.GONE, largeResult.findViewById(R.id.subtext).getVisibility());
+        assertEquals(View.GONE, largeResult.findViewById(R.id.predefined_icon).getVisibility());
+        // Has availability.
+        assertEquals(View.VISIBLE, largeResult.findViewById(R.id.availability).getVisibility());
+        // Has person icon.
+        View personIcon = largeResult.findViewById(R.id.person_icon);
+        assertEquals(View.VISIBLE, personIcon.getVisibility());
+        // Has notification content.
+        statusContent = (TextView) largeResult.findViewById(R.id.text_content);
+        assertEquals(View.VISIBLE, statusContent.getVisibility());
+        assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
+        assertThat(statusContent.getMaxLines()).isEqualTo(3);
+    }
+
+    @Test
+    public void testGetBackgroundTextFromMessageNoPunctuation() {
+        String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("test");
+
+        assertThat(backgroundText).isNull();
+    }
+
+    @Test
+    public void testGetBackgroundTextFromMessageSingleExclamation() {
+        String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("test!");
+
+        assertThat(backgroundText).isNull();
+    }
+
+    @Test
+    public void testGetBackgroundTextFromMessageSingleQuestion() {
+        String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("?test");
+
+        assertThat(backgroundText).isNull();
+    }
+
+    @Test
+    public void testGetBackgroundTextFromMessageSeparatedMarks() {
+        String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("test! right!");
+
+        assertThat(backgroundText).isNull();
+    }
+
+    @Test
+    public void testGetBackgroundTextFromMessageDoubleExclamation() {
+        String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("!!test");
+
+        assertThat(backgroundText).isEqualTo("!");
+    }
+
+    @Test
+    public void testGetBackgroundTextFromMessageDoubleQuestion() {
+        String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("test??");
+
+        assertThat(backgroundText).isEqualTo("?");
+    }
+
+    @Test
+    public void testGetBackgroundTextFromMessageMixed() {
+        String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("test?!");
+
+        assertThat(backgroundText).isEqualTo("!?");
+    }
+
+    @Test
+    public void testGetBackgroundTextFromMessageMixedInTheMiddle() {
+        String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage(
+                "test!? in the middle");
+
+        assertThat(backgroundText).isEqualTo("!?");
+    }
+
+    @Test
+    public void testGetBackgroundTextFromMessageMixedDifferentOrder() {
+        String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage(
+                "test!? in the middle");
+
+        assertThat(backgroundText).isEqualTo("!?");
+    }
+
+    @Test
+    public void testGetBackgroundTextFromMessageMultiple() {
+        String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage(
+                "test!?!!? in the middle");
+
+        assertThat(backgroundText).isEqualTo("!?");
+    }
+
+    @Test
+    public void testGetBackgroundTextFromMessageQuestionFirst() {
+        String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage(
+                "test?? in the middle!!");
+
+        assertThat(backgroundText).isEqualTo("?");
+    }
+
+    @Test
+    public void testGetBackgroundTextFromMessageExclamationFirst() {
+        String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage(
+                "test!! in the middle??");
+
+        assertThat(backgroundText).isEqualTo("!");
+    }
+
+    private int getSizeInDp(int dimenResourceId) {
+        return (int) (mContext.getResources().getDimension(dimenResourceId)
+                / mContext.getResources().getDisplayMetrics().density);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
index 4948c2b..3595095 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
@@ -33,7 +33,6 @@
 import com.android.systemui.qs.carrier.QSCarrierGroup
 import com.android.systemui.qs.carrier.QSCarrierGroupController
 import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusIconContainer
 import com.android.systemui.statusbar.policy.Clock
@@ -78,8 +77,6 @@
     @Mock
     private lateinit var statusBarIconController: StatusBarIconController
     @Mock
-    private lateinit var commandQueue: CommandQueue
-    @Mock
     private lateinit var demoModeController: DemoModeController
     @Mock
     private lateinit var userTracker: UserTracker
@@ -130,7 +127,6 @@
                 uiEventLogger,
                 qsTileHost,
                 statusBarIconController,
-                commandQueue,
                 demoModeController,
                 userTracker,
                 quickQSPanelController,
@@ -233,4 +229,4 @@
         `when`(privacyItemController.micCameraAvailable).thenReturn(micCamera)
         `when`(privacyItemController.locationAvailable).thenReturn(location)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
index e6287e7..aeb5b03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
@@ -18,31 +18,18 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assert.assertNotSame;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
 import android.annotation.Nullable;
-import android.app.Notification;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.drawable.Drawable;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.widget.RemoteViews;
 
 import androidx.palette.graphics.Palette;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.tests.R;
 
 import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -58,17 +45,8 @@
      */
     private static final int COLOR_TOLERANCE = 8;
 
-    private MediaNotificationProcessor mProcessor;
-    private Bitmap mBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
-    private ImageGradientColorizer mColorizer;
     @Nullable private Bitmap mArtwork;
 
-    @Before
-    public void setUp() {
-        mColorizer = spy(new TestableColorizer(mBitmap));
-        mProcessor = new MediaNotificationProcessor(getContext(), getContext(), mColorizer);
-    }
-
     @After
     public void tearDown() {
         if (mArtwork != null) {
@@ -78,53 +56,6 @@
     }
 
     @Test
-    public void testColorizedWithLargeIcon() {
-        Notification.Builder builder = new Notification.Builder(getContext()).setSmallIcon(
-                R.drawable.ic_person)
-                .setContentTitle("Title")
-                .setLargeIcon(mBitmap)
-                .setContentText("Text");
-        Notification notification = builder.build();
-        mProcessor.processNotification(notification, builder);
-        verify(mColorizer).colorize(any(), anyInt(), anyBoolean());
-    }
-
-    @Test
-    public void testNotColorizedWithoutLargeIcon() {
-        Notification.Builder builder = new Notification.Builder(getContext()).setSmallIcon(
-                R.drawable.ic_person)
-                .setContentTitle("Title")
-                .setContentText("Text");
-        Notification notification = builder.build();
-        mProcessor.processNotification(notification, builder);
-        verifyZeroInteractions(mColorizer);
-    }
-
-    @Test
-    public void testRemoteViewsReset() {
-        Notification.Builder builder = new Notification.Builder(getContext()).setSmallIcon(
-                R.drawable.ic_person)
-                .setContentTitle("Title")
-                .setStyle(new Notification.MediaStyle())
-                .setLargeIcon(mBitmap)
-                .setContentText("Text");
-        Notification notification = builder.build();
-        RemoteViews remoteViews = new RemoteViews(getContext().getPackageName(),
-                R.layout.custom_view_dark);
-        notification.contentView = remoteViews;
-        notification.bigContentView = remoteViews;
-        notification.headsUpContentView = remoteViews;
-        mProcessor.processNotification(notification, builder);
-        verify(mColorizer).colorize(any(), anyInt(), anyBoolean());
-        RemoteViews contentView = builder.createContentView();
-        assertNotSame(contentView, remoteViews);
-        contentView = builder.createBigContentView();
-        assertNotSame(contentView, remoteViews);
-        contentView = builder.createHeadsUpContentView();
-        assertNotSame(contentView, remoteViews);
-    }
-
-    @Test
     public void findBackgroundSwatch_white() {
         // Given artwork that is completely white.
         mArtwork = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888);
@@ -153,17 +84,4 @@
         assertThat((float) Color.green(expected)).isWithin(COLOR_TOLERANCE).of(Color.green(actual));
         assertThat((float) Color.blue(expected)).isWithin(COLOR_TOLERANCE).of(Color.blue(actual));
     }
-
-    public static class TestableColorizer extends ImageGradientColorizer {
-        private final Bitmap mBitmap;
-
-        private TestableColorizer(Bitmap bitmap) {
-            mBitmap = bitmap;
-        }
-
-        @Override
-        public Bitmap colorize(Drawable drawable, int backgroundColor, boolean isRtl) {
-            return mBitmap;
-        }
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
deleted file mode 100644
index fbe4d73..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.wrapper;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.app.Notification;
-import android.media.MediaMetadata;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
-import android.provider.Settings;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.View;
-import android.widget.RemoteViews;
-import android.widget.SeekBar;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-public class NotificationMediaTemplateViewWrapperTest extends SysuiTestCase {
-
-    private ExpandableNotificationRow mRow;
-    private Notification mNotif;
-    private View mView;
-    private NotificationMediaTemplateViewWrapper mWrapper;
-
-    @Mock
-    private MetricsLogger mMetricsLogger;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        allowTestableLooperAsMainThread();
-
-        mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
-
-        // These tests are for regular media style notifications, not controls in quick settings
-        Settings.System.putInt(mContext.getContentResolver(), "qs_media_player", 0);
-    }
-
-    private void makeTestNotification(long duration, boolean allowSeeking) throws Exception {
-        Notification.Builder builder = new Notification.Builder(mContext)
-                .setSmallIcon(R.drawable.ic_person)
-                .setContentTitle("Title")
-                .setContentText("Text");
-
-        MediaMetadata metadata = new MediaMetadata.Builder()
-                .putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
-                .build();
-        MediaSession session = new MediaSession(mContext, "TEST_CHANNEL");
-        session.setMetadata(metadata);
-
-        PlaybackState playbackState = new PlaybackState.Builder()
-                .setActions(allowSeeking ? PlaybackState.ACTION_SEEK_TO : 0)
-                .build();
-
-        session.setPlaybackState(playbackState);
-
-        builder.setStyle(new Notification.MediaStyle()
-                .setMediaSession(session.getSessionToken())
-        );
-
-        mNotif = builder.build();
-        assertTrue(mNotif.hasMediaSession());
-
-        NotificationTestHelper helper = new NotificationTestHelper(
-                mContext,
-                mDependency,
-                TestableLooper.get(this));
-        mRow = helper.createRow(mNotif);
-
-        RemoteViews views = new RemoteViews(mContext.getPackageName(),
-                com.android.internal.R.layout.notification_template_material_big_media);
-        mView = views.apply(mContext, null);
-        mWrapper = new NotificationMediaTemplateViewWrapper(mContext,
-                mView, mRow);
-        mWrapper.onContentUpdated(mRow);
-    }
-
-    @Test
-    public void testLogging_NoSeekbar() throws Exception {
-        // Media sessions with duration <= 0 should not include a seekbar
-        makeTestNotification(0, false);
-
-        verify(mMetricsLogger).write(argThat(logMaker ->
-                logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
-                        && logMaker.getType() == MetricsEvent.TYPE_CLOSE
-        ));
-
-        verify(mMetricsLogger, times(0)).write(argThat(logMaker ->
-                logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
-                        && logMaker.getType() == MetricsEvent.TYPE_OPEN
-        ));
-    }
-
-    @Test
-    public void testLogging_HasSeekbarNoScrubber() throws Exception {
-        // Media sessions that do not support seeking should have a seekbar, but no scrubber
-        makeTestNotification(1000, false);
-
-        verify(mMetricsLogger).write(argThat(logMaker ->
-                logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
-                        && logMaker.getType() == MetricsEvent.TYPE_OPEN
-        ));
-
-        // Ensure the callback runs at least once
-        mWrapper.mOnUpdateTimerTick.run();
-
-        verify(mMetricsLogger).write(argThat(logMaker ->
-                logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
-                && logMaker.getType() == MetricsEvent.TYPE_DETAIL
-                && logMaker.getSubtype() == 0
-        ));
-    }
-
-    @Test
-    public void testLogging_HasSeekbarAndScrubber() throws Exception {
-        makeTestNotification(1000, true);
-
-        verify(mMetricsLogger).write(argThat(logMaker ->
-                logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
-                        && logMaker.getType() == MetricsEvent.TYPE_OPEN
-        ));
-
-        verify(mMetricsLogger).write(argThat(logMaker ->
-                logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
-                        && logMaker.getType() == MetricsEvent.TYPE_DETAIL
-                        && logMaker.getSubtype() == 1
-        ));
-    }
-
-    @Test
-    public void testLogging_UpdateSeekbar() throws Exception {
-        makeTestNotification(1000, true);
-
-        SeekBar seekbar = mView.findViewById(
-                com.android.internal.R.id.notification_media_progress_bar);
-        assertTrue(seekbar != null);
-
-        mWrapper.mSeekListener.onStopTrackingTouch(seekbar);
-
-        verify(mMetricsLogger).write(argThat(logMaker ->
-                logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
-                        && logMaker.getType() == MetricsEvent.TYPE_UPDATE));
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 7b7e2d3..f147f1ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -31,7 +31,6 @@
 
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusBarMobileView;
 import com.android.systemui.statusbar.StatusBarWifiView;
@@ -60,14 +59,14 @@
     @Test
     public void testSetCalledOnAdd_IconManager() {
         LinearLayout layout = new LinearLayout(mContext);
-        TestIconManager manager = new TestIconManager(layout, new CommandQueue(mContext));
+        TestIconManager manager = new TestIconManager(layout);
         testCallOnAdd_forManager(manager);
     }
 
     @Test
     public void testSetCalledOnAdd_DarkIconManager() {
         LinearLayout layout = new LinearLayout(mContext);
-        TestDarkIconManager manager = new TestDarkIconManager(layout, new CommandQueue(mContext));
+        TestDarkIconManager manager = new TestDarkIconManager(layout);
         testCallOnAdd_forManager(manager);
     }
 
@@ -104,8 +103,8 @@
     private static class TestDarkIconManager extends DarkIconManager
             implements TestableIconManager {
 
-        TestDarkIconManager(LinearLayout group, CommandQueue commandQueue) {
-            super(group, commandQueue);
+        TestDarkIconManager(LinearLayout group) {
+            super(group);
         }
 
         @Override
@@ -139,8 +138,8 @@
     }
 
     private static class TestIconManager extends IconManager implements TestableIconManager {
-        TestIconManager(ViewGroup group, CommandQueue commandQueue) {
-            super(group, commandQueue);
+        TestIconManager(ViewGroup group) {
+            super(group);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 999d282..8f36415 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -35,7 +35,6 @@
 import static org.mockito.Mockito.when;
 
 import android.app.Instrumentation;
-import android.content.Intent;
 import android.net.ConnectivityManager;
 import android.net.LinkProperties;
 import android.net.Network;
@@ -54,7 +53,6 @@
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
 import android.testing.TestableLooper;
@@ -172,7 +170,7 @@
         mMockNsm = mock(NetworkScoreManager.class);
         mMockSubDefaults = mock(SubscriptionDefaults.class);
         mNetCapabilities = new NetworkCapabilities();
-        when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(true);
+        when(mMockTm.isDataCapable()).thenReturn(true);
         when(mMockTm.createForSubscriptionId(anyInt())).thenReturn(mMockTm);
         doAnswer(invocation -> {
             int rssi = invocation.getArgument(0);
@@ -285,7 +283,7 @@
     }
 
     protected NetworkControllerImpl setUpNoMobileData() {
-        when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
+        when(mMockTm.isDataCapable()).thenReturn(false);
         NetworkControllerImpl networkControllerNoMobile =
                 new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockNsm, mMockSm,
                         mConfig, TestableLooper.get(this).getLooper(), mCallbackHandler,
@@ -308,27 +306,14 @@
                 TelephonyManager.NETWORK_TYPE_UMTS);
         setConnectivityViaCallbackInNetworkController(
                 NetworkCapabilities.TRANSPORT_CELLULAR, true, true, null);
-        setConnectivityViaBroadcast(
-                NetworkCapabilities.TRANSPORT_CELLULAR, true, true);
     }
 
-    public void setConnectivityViaBroadcastForVcn(
+    public void setConnectivityViaCallbackInNetworkControllerForVcn(
             int networkType, boolean validated, boolean isConnected, VcnTransportInfo info) {
         mNetCapabilities.setTransportInfo(info);
         setConnectivityCommon(networkType, validated, isConnected);
         mDefaultCallbackInNetworkController.onCapabilitiesChanged(
                 mock(Network.class), new NetworkCapabilities(mNetCapabilities));
-        Intent i = new Intent(ConnectivityManager.INET_CONDITION_ACTION);
-        mNetworkController.onReceive(mContext, i);
-    }
-
-    public void setConnectivityViaBroadcast(
-        int networkType, boolean validated, boolean isConnected) {
-        setConnectivityCommon(networkType, validated, isConnected);
-        mDefaultCallbackInNetworkController.onCapabilitiesChanged(
-                mock(Network.class), new NetworkCapabilities(mNetCapabilities));
-        Intent i = new Intent(ConnectivityManager.INET_CONDITION_ACTION);
-        mNetworkController.onReceive(mContext, i);
     }
 
     public void setConnectivityViaCallbackInNetworkController(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 37b6a5d..b108dd8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -126,7 +126,8 @@
         when(mMockTm.isDataConnectionAllowed()).thenReturn(false);
         setupDefaultSignal();
         updateDataConnectionState(TelephonyManager.DATA_CONNECTED, 0);
-        setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
+        setConnectivityViaCallbackInNetworkController(
+                NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null);
 
         // Verify that a SignalDrawable with a cut out is used to display data disabled.
         verifyLastMobileDataIndicators(false, DEFAULT_SIGNAL_STRENGTH, 0,
@@ -140,7 +141,8 @@
         when(mMockTm.isDataConnectionAllowed()).thenReturn(false);
         setupDefaultSignal();
         updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
-        setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
+        setConnectivityViaCallbackInNetworkController(
+                NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null);
 
         // Verify that a SignalDrawable with a cut out is used to display data disabled.
         verifyLastMobileDataIndicators(false, DEFAULT_SIGNAL_STRENGTH, 0,
@@ -155,7 +157,8 @@
         setupDefaultSignal();
         setDefaultSubId(mSubId + 1);
         updateDataConnectionState(TelephonyManager.DATA_CONNECTED, 0);
-        setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
+        setConnectivityViaCallbackInNetworkController(
+                NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null);
 
         // Verify that a SignalDrawable with a cut out is used to display data disabled.
         verifyLastMobileDataIndicators(false, DEFAULT_SIGNAL_STRENGTH, 0,
@@ -170,7 +173,8 @@
         setupDefaultSignal();
         setDefaultSubId(mSubId + 1);
         updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
-        setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
+        setConnectivityViaCallbackInNetworkController(
+                NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null);
 
         // Verify that a SignalDrawable with a cut out is used to display data disabled.
         verifyLastMobileDataIndicators(false, DEFAULT_SIGNAL_STRENGTH, 0,
@@ -184,7 +188,8 @@
         when(mMockTm.isDataConnectionAllowed()).thenReturn(false);
         setupDefaultSignal();
         updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
-        setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
+        setConnectivityViaCallbackInNetworkController(
+                NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null);
         when(mMockProvisionController.isUserSetup(anyInt())).thenReturn(false);
         mUserCallback.onUserSetupChanged();
         TestableLooper.get(this).processAllMessages();
@@ -206,7 +211,8 @@
         mConfig.alwaysShowDataRatIcon = true;
         mNetworkController.handleConfigurationChanged();
 
-        setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
+        setConnectivityViaCallbackInNetworkController(
+                NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null);
         verifyLastMobileDataIndicators(false, DEFAULT_SIGNAL_STRENGTH, TelephonyIcons.ICON_G,
                 true, DEFAULT_QS_SIGNAL_STRENGTH, 0, false, false);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerEthernetTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerEthernetTest.java
index 93cf3e8..6aab9c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerEthernetTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerEthernetTest.java
@@ -37,7 +37,8 @@
     }
 
     protected void setEthernetState(boolean connected, boolean validated) {
-        setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_ETHERNET, validated, connected);
+        setConnectivityViaCallbackInNetworkController(
+                NetworkCapabilities.TRANSPORT_ETHERNET, validated, connected, null);
     }
 
     protected void verifyLastEthernetIcon(boolean visible, int icon) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index c0d9c3d..91e9f06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -23,8 +23,8 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Intent;
-import android.net.ConnectivityManager;
 import android.net.NetworkCapabilities;
+import android.net.wifi.WifiInfo;
 import android.os.Handler;
 import android.os.Looper;
 import android.telephony.CellSignalStrength;
@@ -59,7 +59,7 @@
     @Test
     public void testNoIconWithoutMobile() {
         // Turn off mobile network support.
-        when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
+        when(mMockTm.isDataCapable()).thenReturn(false);
         // Create a new NetworkController as this is currently handled in constructor.
         mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
                 mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
@@ -145,7 +145,7 @@
     @Test
     public void testNoSimlessIconWithoutMobile() {
         // Turn off mobile network support.
-        when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
+        when(mMockTm.isDataCapable()).thenReturn(false);
         // Create a new NetworkController as this is currently handled in constructor.
         mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
                 mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
@@ -172,7 +172,8 @@
                     testStrength, DEFAULT_ICON);
 
             // Verify low inet number indexing.
-            setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, true);
+            setConnectivityViaCallbackInNetworkController(
+                    NetworkCapabilities.TRANSPORT_CELLULAR, false, true, null);
             verifyLastMobileDataIndicators(true,
                     testStrength, DEFAULT_ICON, false, false);
         }
@@ -259,8 +260,10 @@
     @Test
     public void testNoBangWithWifi() {
         setupDefaultSignal();
-        setConnectivityViaBroadcast(mMobileSignalController.mTransportType, false, false);
-        setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
+        setConnectivityViaCallbackInNetworkController(
+                mMobileSignalController.mTransportType, false, false, null);
+        setConnectivityViaCallbackInNetworkController(
+                NetworkCapabilities.TRANSPORT_WIFI, true, true, mock(WifiInfo.class));
 
         verifyLastMobileDataIndicators(false, DEFAULT_LEVEL, 0);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 092c116..ab7cbf7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -59,9 +59,11 @@
         for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
             setWifiLevel(testLevel);
 
-            setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
+            setConnectivityViaCallbackInNetworkController(
+                    NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
             verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
-            setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, false, true);
+            setConnectivityViaCallbackInNetworkController(
+                    NetworkCapabilities.TRANSPORT_WIFI, false, true, mWifiInfo);
             // Icon does not show if not validated
             verifyLastWifiIcon(false, WifiIcons.WIFI_SIGNAL_STRENGTH[0][testLevel]);
         }
@@ -80,12 +82,14 @@
         setWifiState(true, testSsid);
         for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
             setWifiLevel(testLevel);
-            setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
+            setConnectivityViaCallbackInNetworkController(
+                    NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
             setConnectivityViaDefaultCallbackInWifiTracker(
                     NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
             verifyLastQsWifiIcon(true, true, WifiIcons.QS_WIFI_SIGNAL_STRENGTH[1][testLevel],
                     testSsid);
-            setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, false, true);
+            setConnectivityViaCallbackInNetworkController(
+                    NetworkCapabilities.TRANSPORT_WIFI, false, true, mWifiInfo);
             verifyLastQsWifiIcon(true, true, WifiIcons.QS_WIFI_SIGNAL_STRENGTH[0][testLevel],
                     testSsid);
         }
@@ -99,7 +103,8 @@
         setWifiEnabled(true);
         setWifiState(true, testSsid);
         setWifiLevel(testLevel);
-        setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
+        setConnectivityViaCallbackInNetworkController(
+                NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
         setConnectivityViaDefaultCallbackInWifiTracker(
                 NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
         verifyLastQsWifiIcon(true, true,
@@ -126,14 +131,17 @@
         setWifiEnabled(true);
         setWifiState(true, testSsid);
         setWifiLevel(testLevel);
-        setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
+        setConnectivityViaCallbackInNetworkController(
+                NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
         verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
 
         setupDefaultSignal();
         setGsmRoaming(true);
         // Still be on wifi though.
-        setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
-        setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
+        setConnectivityViaCallbackInNetworkController(
+                NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
+        setConnectivityViaCallbackInNetworkController(
+                NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null);
         verifyLastMobileDataIndicators(true, DEFAULT_LEVEL, 0, true);
     }
 
@@ -145,7 +153,8 @@
         setWifiEnabled(true);
         setWifiState(true, testSsid);
         setWifiLevel(testLevel);
-        setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
+        setConnectivityViaCallbackInNetworkController(
+                NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
         verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
 
         setConnectivityViaCallbackInNetworkController(
@@ -161,7 +170,8 @@
         setWifiEnabled(true);
         setWifiState(true, testSsid);
         setWifiLevel(testLevel);
-        setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
+        setConnectivityViaCallbackInNetworkController(
+                NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
         verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
 
         setWifiState(false, testSsid);
@@ -234,11 +244,11 @@
         for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
             setWifiLevelForVcn(testLevel);
 
-            setConnectivityViaBroadcastForVcn(
+            setConnectivityViaCallbackInNetworkControllerForVcn(
                     NetworkCapabilities.TRANSPORT_CELLULAR, true, true, mVcnTransportInfo);
             verifyLastMobileDataIndicatorsForVcn(true, testLevel, TelephonyIcons.ICON_CWF, true);
 
-            setConnectivityViaBroadcastForVcn(
+            setConnectivityViaCallbackInNetworkControllerForVcn(
                     NetworkCapabilities.TRANSPORT_CELLULAR, false, true, mVcnTransportInfo);
             verifyLastMobileDataIndicatorsForVcn(true, testLevel, TelephonyIcons.ICON_CWF, false);
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
index 6067b42..eb6fc2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
@@ -23,7 +23,6 @@
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_SETTINGS;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_SYSUI;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_THEME_PICKER;
-import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_NEUTRAL_PALETTE;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SHAPE;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
 import static com.android.systemui.theme.ThemeOverlayApplier.SETTINGS_PACKAGE;
@@ -116,8 +115,6 @@
                                 ANDROID_PACKAGE, OVERLAY_CATEGORY_ACCENT_COLOR, false),
                         createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_SYSTEM_PALETTE,
                                 ANDROID_PACKAGE, OVERLAY_CATEGORY_SYSTEM_PALETTE, false),
-                        createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_NEUTRAL_PALETTE,
-                                ANDROID_PACKAGE, OVERLAY_CATEGORY_NEUTRAL_PALETTE, false),
                         createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_FONT,
                                 ANDROID_PACKAGE, OVERLAY_CATEGORY_FONT, false),
                         createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_SHAPE,
@@ -128,8 +125,6 @@
                                 ANDROID_PACKAGE, OVERLAY_CATEGORY_ACCENT_COLOR, true),
                         createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_SYSTEM_PALETTE,
                                 ANDROID_PACKAGE, OVERLAY_CATEGORY_SYSTEM_PALETTE, true),
-                        createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_NEUTRAL_PALETTE,
-                                ANDROID_PACKAGE, OVERLAY_CATEGORY_NEUTRAL_PALETTE, true),
                         createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_FONT,
                                 ANDROID_PACKAGE, OVERLAY_CATEGORY_FONT, true),
                         createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_SHAPE,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 8a0ac11..4e7e0a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -17,7 +17,6 @@
 package com.android.systemui.theme;
 
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
-import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_NEUTRAL_PALETTE;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -147,8 +146,6 @@
         // Assert that we received the colors that we were expecting
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
                 .isEqualTo(new OverlayIdentifier("ffff0000"));
-        assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_NEUTRAL_PALETTE))
-                .isEqualTo(new OverlayIdentifier("ffff0000"));
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR))
                 .isEqualTo(new OverlayIdentifier("ff0000ff"));
 
diff --git a/proto/src/camera.proto b/proto/src/camera.proto
new file mode 100644
index 0000000..d07a525
--- /dev/null
+++ b/proto/src/camera.proto
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package android.stats.camera;
+
+option java_package = "android.stats.camera";
+option java_outer_classname = "CameraProtos";
+
+/**
+ * CameraStreamProto from atoms.proto, duplicated here so that it's accessible from the
+ * logging code. Must be kept in sync with the definition in atoms.proto.
+ */
+message CameraStreamProto {
+    // The stream width (in pixels)
+    optional int32 width = 1;
+    // The stream height (in pixels)
+    optional int32 height = 2;
+    // The format of the stream
+    optional int32 format = 3;
+    // The dataspace of the stream
+    optional int32 data_space = 4;
+    // The usage flag of the stream
+    optional int64 usage = 5;
+
+    // The number of requests for this stream
+    optional int64 request_count = 6;
+    // The number of buffer error for this stream
+    optional int64 error_count = 7;
+    // The capture latency of first request for this stream
+    optional int32 first_capture_latency_millis = 8;
+
+    // The maximum number of hal buffers
+    optional int32 max_hal_buffers = 9;
+    // The maximum number of app buffers
+    optional int32 max_app_buffers = 10;
+
+    // Type of stream histogram
+    // 1: Capture latency: bin size in milliseconds
+    enum HistogramType {
+        UNKNOWN = 0;
+        CAPTURE_LATENCY = 1;
+    }
+    optional HistogramType histogram_type = 11;
+    // The boundary values between histogram bins
+    // Expected number of fields: 9
+    repeated float histogram_bins = 12;
+    // The frame counts for each histogram bins
+    // Expected number of fields: 10
+    repeated int64 histogram_counts = 13;
+}
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
index e9a099a..2c50389 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
@@ -35,6 +35,7 @@
 import android.content.pm.ParceledListSlice;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.Process;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.util.Slog;
@@ -179,7 +180,8 @@
             Context ctx = getContext();
             if (!(ctx.checkCallingPermission(PACKAGE_USAGE_STATS) == PERMISSION_GRANTED
                     || mServiceNameResolver.isTemporary(userId)
-                    || mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()))) {
+                    || mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())
+                    || Binder.getCallingUid() == Process.SYSTEM_UID)) {
 
                 String msg = "Permission Denial: " + func + " from pid="
                         + Binder.getCallingPid()
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
index 735f420..cd332a6 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
@@ -40,6 +40,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.infra.AbstractRemoteService;
 import com.android.server.LocalServices;
 import com.android.server.infra.AbstractPerUserSystemService;
@@ -55,6 +56,8 @@
     private static final String TAG = AppPredictionPerUserService.class.getSimpleName();
     private static final String PREDICT_USING_PEOPLE_SERVICE_PREFIX =
             "predict_using_people_service_";
+    private static final String REMOTE_APP_PREDICTOR_KEY = "remote_app_predictor";
+
 
     @Nullable
     @GuardedBy("mLock")
@@ -112,8 +115,16 @@
     @GuardedBy("mLock")
     public void onCreatePredictionSessionLocked(@NonNull AppPredictionContext context,
             @NonNull AppPredictionSessionId sessionId, @NonNull IBinder token) {
-        final boolean usesPeopleService = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
+        boolean usesPeopleService = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
                 PREDICT_USING_PEOPLE_SERVICE_PREFIX + context.getUiSurface(), false);
+        if (context.getExtras() != null
+                && context.getExtras().getBoolean(REMOTE_APP_PREDICTOR_KEY, false)
+                && DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED, false)
+        ) {
+            // connect with remote AppPredictionService instead for dark launch
+            usesPeopleService = false;
+        }
         final boolean serviceExists = resolveService(sessionId, false,
                 usesPeopleService, s -> s.onCreatePredictionSession(context, sessionId));
         if (serviceExists && !mSessionInfos.containsKey(sessionId)) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 9ac93d9..b160d78 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -1205,8 +1205,6 @@
         }
 
         public void schedule() {
-            Slog.d(LOG_TAG,
-                    "TriggerDeviceDisappearedRunnable.schedule(address = " + mAddress + ")");
             mMainHandler.removeCallbacks(this);
             mMainHandler.postDelayed(this, this, DEVICE_DISAPPEARED_TIMEOUT_MS);
         }
@@ -1244,8 +1242,6 @@
     }
 
     private void onDeviceNearby(String address) {
-        Slog.i(LOG_TAG, "onDeviceNearby(address = " + address + ")");
-
         Date timestamp = new Date();
         Date oldTimestamp = mDevicesLastNearby.put(address, timestamp);
 
@@ -1259,13 +1255,11 @@
         boolean justAppeared = oldTimestamp == null
                 || timestamp.getTime() - oldTimestamp.getTime() >= DEVICE_DISAPPEARED_TIMEOUT_MS;
         if (justAppeared) {
+            Slog.i(LOG_TAG, "onDeviceNearby(justAppeared, address = " + address + ")");
             for (Association association : getAllAssociations(address)) {
                 if (association.isNotifyOnDeviceNearby()) {
-                    if (DEBUG) {
-                        Slog.i(LOG_TAG, "Device " + address
-                                + " managed by " + association.getPackageName()
-                                + " is nearby on " + timestamp);
-                    }
+                    Slog.i(LOG_TAG,
+                            "Sending onDeviceAppeared to " + association.getPackageName() + ")");
                     getDeviceListenerServiceConnector(association).run(
                             service -> service.onDeviceAppeared(association.getDeviceMacAddress()));
                 }
@@ -1279,12 +1273,8 @@
         boolean hasDeviceListeners = false;
         for (Association association : getAllAssociations(address)) {
             if (association.isNotifyOnDeviceNearby()) {
-                if (DEBUG) {
-                    Slog.i(LOG_TAG, "Device " + address
-                            + " managed by " + association.getPackageName()
-                            + " disappeared; last seen on " + mDevicesLastNearby.get(address));
-                }
-
+                Slog.i(LOG_TAG,
+                        "Sending onDeviceDisappeared to " + association.getPackageName() + ")");
                 getDeviceListenerServiceConnector(association).run(
                         service -> service.onDeviceDisappeared(address));
                 hasDeviceListeners = true;
diff --git a/services/core/Android.bp b/services/core/Android.bp
index ed2e625..5f7016e 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -222,10 +222,12 @@
     srcs: [
         "java/com/android/server/ConnectivityService.java",
         "java/com/android/server/ConnectivityServiceInitializer.java",
+        "java/com/android/server/NetIdManager.java",
         "java/com/android/server/TestNetworkService.java",
         "java/com/android/server/connectivity/AutodestructReference.java",
         "java/com/android/server/connectivity/ConnectivityConstants.java",
         "java/com/android/server/connectivity/DnsManager.java",
+        "java/com/android/server/connectivity/FullScore.java",
         "java/com/android/server/connectivity/KeepaliveTracker.java",
         "java/com/android/server/connectivity/LingerMonitor.java",
         "java/com/android/server/connectivity/MockableSystemProperties.java",
@@ -234,7 +236,9 @@
         "java/com/android/server/connectivity/NetworkDiagnostics.java",
         "java/com/android/server/connectivity/NetworkNotificationManager.java",
         "java/com/android/server/connectivity/NetworkRanker.java",
+        "java/com/android/server/connectivity/OsCompat.java",
         "java/com/android/server/connectivity/PermissionMonitor.java",
+        "java/com/android/server/connectivity/ProfileNetworkPreferences.java",
         "java/com/android/server/connectivity/ProxyTracker.java",
         "java/com/android/server/connectivity/QosCallbackAgentConnection.java",
         "java/com/android/server/connectivity/QosCallbackTracker.java",
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 4db4d1d..fbba189 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -87,7 +87,6 @@
 import static java.util.Map.Entry;
 
 import android.Manifest;
-import android.annotation.BoolRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
@@ -146,7 +145,6 @@
 import android.net.NetworkScore;
 import android.net.NetworkSpecifier;
 import android.net.NetworkStack;
-import android.net.NetworkStackClient;
 import android.net.NetworkState;
 import android.net.NetworkStateSnapshot;
 import android.net.NetworkTestResultParcelable;
@@ -173,13 +171,14 @@
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkEvent;
 import android.net.netlink.InetDiagMessage;
+import android.net.networkstack.ModuleNetworkStackClient;
+import android.net.networkstack.NetworkStackClientBase;
 import android.net.resolv.aidl.DnsHealthEventParcel;
 import android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener;
 import android.net.resolv.aidl.Nat64PrefixEventParcel;
 import android.net.resolv.aidl.PrivateDnsValidationEventParcel;
 import android.net.shared.PrivateDnsConfig;
 import android.net.util.MultinetworkPolicyTracker;
-import android.net.util.NetdService;
 import android.os.BatteryStatsManager;
 import android.os.Binder;
 import android.os.Build;
@@ -1122,10 +1121,10 @@
         }
 
         /**
-         * Get a reference to the NetworkStackClient.
+         * Get a reference to the ModuleNetworkStackClient.
          */
-        public NetworkStackClient getNetworkStack() {
-            return NetworkStackClient.getInstance();
+        public NetworkStackClientBase getNetworkStack() {
+            return ModuleNetworkStackClient.getInstance(null);
         }
 
         /**
@@ -1184,7 +1183,8 @@
 
     public ConnectivityService(Context context) {
         this(context, getDnsResolver(context), new IpConnectivityLog(),
-                NetdService.getInstance(), new Dependencies());
+                INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)),
+                new Dependencies());
     }
 
     @VisibleForTesting
@@ -1389,7 +1389,7 @@
         mHandler.sendEmptyMessage(EVENT_PRIVATE_DNS_SETTINGS_CHANGED);
     }
 
-    private void handleAlwaysOnNetworkRequest(NetworkRequest networkRequest, @BoolRes int id) {
+    private void handleAlwaysOnNetworkRequest(NetworkRequest networkRequest, int id) {
         final boolean enable = mContext.getResources().getBoolean(id);
         handleAlwaysOnNetworkRequest(networkRequest, enable);
     }
@@ -2905,10 +2905,6 @@
         }
 
         pw.println();
-        pw.println("NetworkStackClient logs:");
-        pw.increaseIndent();
-        NetworkStackClient.getInstance().dump(pw);
-        pw.decreaseIndent();
 
         pw.println();
         pw.println("Permission Monitor:");
@@ -5090,7 +5086,7 @@
 
     @Override
     public void setRequireVpnForUids(boolean requireVpn, UidRange[] ranges) {
-        PermissionUtils.enforceNetworkStackPermission(mContext);
+        enforceNetworkStackOrSettingsPermission();
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_REQUIRE_VPN_FOR_UIDS,
                 encodeBool(requireVpn), 0 /* arg2 */, ranges));
     }
@@ -5128,7 +5124,7 @@
 
     @Override
     public void setLegacyLockdownVpnEnabled(boolean enabled) {
-        enforceSettingsPermission();
+        enforceNetworkStackOrSettingsPermission();
         mHandler.post(() -> mLockdownEnabled = enabled);
     }
 
@@ -6121,10 +6117,15 @@
     private NetworkCapabilities copyDefaultNetworkCapabilitiesForUid(
             @NonNull final NetworkCapabilities netCapToCopy, @NonNull final int requestorUid,
             @NonNull final String requestorPackageName) {
+        // These capabilities are for a TRACK_DEFAULT callback, so:
+        // 1. Remove NET_CAPABILITY_VPN, because it's (currently!) the only difference between
+        //    mDefaultRequest and a per-UID default request.
+        //    TODO: stop depending on the fact that these two unrelated things happen to be the same
+        // 2. Always set the UIDs to mAsUid. restrictRequestUidsForCallerAndSetRequestorInfo will
+        //    not do this in the case of a privileged application.
         final NetworkCapabilities netCap = new NetworkCapabilities(netCapToCopy);
         netCap.removeCapability(NET_CAPABILITY_NOT_VPN);
         netCap.setSingleUid(requestorUid);
-        netCap.setUids(new ArraySet<>());
         restrictRequestUidsForCallerAndSetRequestorInfo(
                 netCap, requestorUid, requestorPackageName);
         return netCap;
@@ -8410,7 +8411,7 @@
         }
     }
 
-    private @VpnManager.VpnType int getVpnType(@Nullable NetworkAgentInfo vpn) {
+    private int getVpnType(@Nullable NetworkAgentInfo vpn) {
         if (vpn == null) return VpnManager.TYPE_VPN_NONE;
         final TransportInfo ti = vpn.networkCapabilities.getTransportInfo();
         if (!(ti instanceof VpnTransportInfo)) return VpnManager.TYPE_VPN_NONE;
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index f04af8b..0c3d884 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -448,7 +448,7 @@
                 final GZIPOutputStream gzipOutputStream =
                         new GZIPOutputStream(new FileOutputStream(fd));
                 FileUtils.copy(in, gzipOutputStream);
-                gzipOutputStream.finish();
+                gzipOutputStream.close();
             } else {
                 FileUtils.copy(in, new FileOutputStream(fd));
             }
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index a09dbc7..ceb12c8 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -21,6 +21,21 @@
                 }
             ],
             "file_patterns": ["NotificationManagerService\\.java"]
+        },
+        {
+            "name": "CtsContentTestCases",
+            "options": [
+                {
+                    "include-filter": "android.content.cts.ClipboardManagerTest"
+                },
+                {
+                    "include-filter": "android.content.cts.ClipDataTest"
+                },
+                {
+                    "include-filter": "android.content.cts.ClipDescriptionTest"
+                }
+            ],
+            "file_patterns": ["ClipboardService\\.java"]
         }
     ],
     "presubmit-large": [
diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java
index f566277..09873f4 100644
--- a/services/core/java/com/android/server/TestNetworkService.java
+++ b/services/core/java/com/android/server/TestNetworkService.java
@@ -35,7 +35,6 @@
 import android.net.RouteInfo;
 import android.net.TestNetworkInterface;
 import android.net.TestNetworkSpecifier;
-import android.net.util.NetdService;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -86,7 +85,9 @@
         mHandler = new Handler(mHandlerThread.getLooper());
 
         mContext = Objects.requireNonNull(context, "missing Context");
-        mNetd = Objects.requireNonNull(NetdService.getInstance(), "could not get netd instance");
+        mNetd = Objects.requireNonNull(
+                INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)),
+                "could not get netd instance");
         mCm = mContext.getSystemService(ConnectivityManager.class);
         mNetworkProvider = new NetworkProvider(mContext, mHandler.getLooper(),
                 TEST_NETWORK_PROVIDER_NAME);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 1b352c7..d2fd8ff 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -5663,8 +5663,8 @@
         }
 
         if (ret == REASON_DENIED) {
-            if (mAm.checkPermission(SYSTEM_ALERT_WINDOW, callingPid,
-                    callingUid) == PERMISSION_GRANTED) {
+            if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,
+                    callingPackage)) {
                 ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
             }
         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c4548a3..492759f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -182,6 +182,7 @@
 import android.app.WaitResult;
 import android.app.backup.BackupManager.OperationType;
 import android.app.backup.IBackupManager;
+import android.app.job.JobParameters;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStatsManager;
@@ -3490,7 +3491,9 @@
 
                     // Clear its scheduled jobs
                     JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
-                    js.cancelJobsForUid(appInfo.uid, "clear data");
+                    // Clearing data is akin to uninstalling. The app is force stopped before we
+                    // get to this point, so the reason won't be checked by the app.
+                    js.cancelJobsForUid(appInfo.uid, JobParameters.STOP_REASON_USER, "clear data");
 
                     // Clear its pending alarms
                     AlarmManagerInternal ami = LocalServices.getService(AlarmManagerInternal.class);
diff --git a/services/core/java/com/android/server/am/AssistDataRequester.java b/services/core/java/com/android/server/am/AssistDataRequester.java
index d8d6528..70a54cf 100644
--- a/services/core/java/com/android/server/am/AssistDataRequester.java
+++ b/services/core/java/com/android/server/am/AssistDataRequester.java
@@ -143,27 +143,45 @@
      * Request that autofill data be loaded asynchronously. The resulting data will be provided
      * through the {@link AssistDataRequesterCallbacks}.
      *
-     * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String)}.
+     * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String,
+     * boolean)}.
      */
     public void requestAutofillData(List<IBinder> activityTokens, int callingUid,
             String callingPackage) {
         requestData(activityTokens, true /* requestAutofillData */,
                 true /* fetchData */, false /* fetchScreenshot */,
                 true /* allowFetchData */, false /* allowFetchScreenshot */,
-                callingUid, callingPackage);
+                false /* ignoreTopActivityCheck */, callingUid, callingPackage);
     }
 
     /**
      * Request that assist data be loaded asynchronously. The resulting data will be provided
      * through the {@link AssistDataRequesterCallbacks}.
      *
-     * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String)}.
+     * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String,
+     * boolean)}.
      */
     public void requestAssistData(List<IBinder> activityTokens, final boolean fetchData,
             final boolean fetchScreenshot, boolean allowFetchData, boolean allowFetchScreenshot,
             int callingUid, String callingPackage) {
+        requestAssistData(activityTokens, fetchData, fetchScreenshot, allowFetchData,
+                allowFetchScreenshot, false /* ignoreTopActivityCheck */, callingUid,
+                callingPackage);
+    }
+
+    /**
+     * Request that assist data be loaded asynchronously. The resulting data will be provided
+     * through the {@link AssistDataRequesterCallbacks}.
+     *
+     * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String,
+     * boolean)}.
+     */
+    public void requestAssistData(List<IBinder> activityTokens, final boolean fetchData,
+            final boolean fetchScreenshot, boolean allowFetchData, boolean allowFetchScreenshot,
+            boolean ignoreTopActivityCheck, int callingUid, String callingPackage) {
         requestData(activityTokens, false /* requestAutofillData */, fetchData, fetchScreenshot,
-                allowFetchData, allowFetchScreenshot, callingUid, callingPackage);
+                allowFetchData, allowFetchScreenshot, ignoreTopActivityCheck, callingUid,
+                callingPackage);
     }
 
     /**
@@ -183,10 +201,13 @@
      *     is allowed to fetch the assist data
      * @param allowFetchScreenshot to be joined with other checks, determines whether or not the
      *     requester is allowed to fetch the assist screenshot
+     * @param ignoreTopActivityCheck overrides the check for whether the activity is in focus when
+     *     making the request. Used when passing an activity from Recents.
      */
     private void requestData(List<IBinder> activityTokens, final boolean requestAutofillData,
             final boolean fetchData, final boolean fetchScreenshot, boolean allowFetchData,
-            boolean allowFetchScreenshot, int callingUid, String callingPackage) {
+            boolean allowFetchScreenshot, boolean ignoreTopActivityCheck, int callingUid,
+            String callingPackage) {
         // TODO(b/34090158): Known issue, if the assist data is not allowed on the current activity,
         //                   then no assist data is requested for any of the other activities
 
@@ -230,7 +251,8 @@
                                         receiverExtras, topActivity, 0 /* flags */)
                                 : mActivityTaskManager.requestAssistContextExtras(
                                         ASSIST_CONTEXT_FULL, this, receiverExtras, topActivity,
-                                        /* focused= */ i == 0, /* newSessionId= */ i == 0);
+                                        /* checkActivityIsTop= */ (i == 0)
+                                        && !ignoreTopActivityCheck, /* newSessionId= */ i == 0);
                         if (result) {
                             mPendingDataCount++;
                         } else if (i == 0) {
diff --git a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
index 4c9ab63..d789bbb 100644
--- a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
+++ b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
@@ -241,7 +241,7 @@
     /**
      * For a consumer of type {@link EnergyConsumerType#OTHER}, updates
      * {@link #mAttributionSnapshots} with freshly measured energies (per uid) and returns the
-     * charge consumed (in microcouloumbs) between the previously stored values and the passed-in
+     * charge consumed (in microcoulombs) between the previously stored values and the passed-in
      * values.
      *
      * @param consumerInfo a consumer of type {@link EnergyConsumerType#OTHER}.
@@ -341,11 +341,11 @@
         return numOrdinals;
     }
 
-    /** Calculate charge consumption (in microcouloumbs) from a given energy and voltage */
+    /** Calculate charge consumption (in microcoulombs) from a given energy and voltage */
     private long calculateChargeConsumedUC(long deltaEnergyUJ, int avgVoltageMV) {
         // To overflow, a 3.7V 10000mAh battery would need to completely drain 69244 times
-        // since the last snapshot. Round up to the nearest whole long.
-        return (deltaEnergyUJ * MILLIVOLTS_PER_VOLT + (avgVoltageMV + 1) / 2) / avgVoltageMV;
+        // since the last snapshot. Round off to the nearest whole long.
+        return (deltaEnergyUJ * MILLIVOLTS_PER_VOLT + (avgVoltageMV / 2)) / avgVoltageMV;
     }
 
 }
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index ad5a65c..c6824d1 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -293,6 +293,35 @@
     }
 
     /**
+     * Get the hibernating packages for the given user. This is equivalent to the list of
+     * packages for the user that return true for {@link #isHibernatingForUser}.
+     */
+    @NonNull List<String> getHibernatingPackagesForUser(int userId) {
+        ArrayList<String> hibernatingPackages = new ArrayList<>();
+        if (!checkHibernationEnabled("getHibernatingPackagesForUser")) {
+            return hibernatingPackages;
+        }
+        getContext().enforceCallingOrSelfPermission(
+                android.Manifest.permission.MANAGE_APP_HIBERNATION,
+                "Caller does not have MANAGE_APP_HIBERNATION permission.");
+        userId = handleIncomingUser(userId, "getHibernatingPackagesForUser");
+        if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
+            Slog.w(TAG, "Attempt to get hibernating packages for a stopped or nonexistent user "
+                    + userId);
+            return hibernatingPackages;
+        }
+        synchronized (mLock) {
+            Map<String, UserLevelState> userStates = mUserStates.get(userId);
+            for (UserLevelState state : userStates.values()) {
+                if (state.hibernated) {
+                    hibernatingPackages.add(state.packageName);
+                }
+            }
+            return hibernatingPackages;
+        }
+    }
+
+    /**
      * Put an app into hibernation for a given user, allowing user-level optimizations to occur.
      *
      * @param pkgState package hibernation state
@@ -610,6 +639,11 @@
         }
 
         @Override
+        public List<String> getHibernatingPackagesForUser(int userId) {
+            return mService.getHibernatingPackagesForUser(userId);
+        }
+
+        @Override
         public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
                 @Nullable FileDescriptor err, @NonNull String[] args,
                 @Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver) {
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 81ce2d5..3cfaaf7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -197,7 +197,7 @@
         return mListener;
     }
 
-    public final int getTargetUserId() {
+    public int getTargetUserId() {
         return mTargetUserId;
     }
 
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 20c25c3..6c480f1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -55,7 +55,7 @@
 
     private static final String BASE_TAG = "BiometricScheduler";
     // Number of recent operations to keep in our logs for dumpsys
-    private static final int LOG_NUM_RECENT_OPERATIONS = 50;
+    protected static final int LOG_NUM_RECENT_OPERATIONS = 50;
 
     /**
      * Contains all the necessary information for a HAL operation.
@@ -196,10 +196,10 @@
         }
     }
 
-    @NonNull private final String mBiometricTag;
+    @NonNull protected final String mBiometricTag;
     @Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
     @NonNull private final IBiometricService mBiometricService;
-    @NonNull private final Handler mHandler = new Handler(Looper.getMainLooper());
+    @NonNull protected final Handler mHandler = new Handler(Looper.getMainLooper());
     @NonNull private final InternalCallback mInternalCallback;
     @VisibleForTesting @NonNull final Deque<Operation> mPendingOperations;
     @VisibleForTesting @Nullable Operation mCurrentOperation;
@@ -294,11 +294,11 @@
         return mInternalCallback;
     }
 
-    private String getTag() {
+    protected String getTag() {
         return BASE_TAG + "/" + mBiometricTag;
     }
 
-    private void startNextOperationIfIdle() {
+    protected void startNextOperationIfIdle() {
         if (mCurrentOperation != null) {
             Slog.v(getTag(), "Not idle, current operation: " + mCurrentOperation);
             return;
@@ -310,6 +310,7 @@
 
         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.
diff --git a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
new file mode 100644
index 0000000..8b9be83
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
@@ -0,0 +1,50 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.os.IBinder;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.BiometricsProto;
+
+public abstract class StartUserClient<T>  extends HalClientMonitor<T> {
+
+    public interface UserStartedCallback {
+        void onUserStarted(int newUserId);
+    }
+
+    @NonNull @VisibleForTesting
+    protected final UserStartedCallback mUserStartedCallback;
+
+    public StartUserClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+            @Nullable IBinder token, int userId, int sensorId,
+            @NonNull UserStartedCallback callback) {
+        super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
+                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
+                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+        mUserStartedCallback = callback;
+    }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_START_USER;
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
new file mode 100644
index 0000000..62cd673
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
@@ -0,0 +1,50 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.os.IBinder;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.BiometricsProto;
+
+public abstract class StopUserClient<T> extends HalClientMonitor<T> {
+
+    public interface UserStoppedCallback {
+        void onUserStopped();
+    }
+
+    @NonNull @VisibleForTesting
+    protected final UserStoppedCallback mUserStoppedCallback;
+
+    public StopUserClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+            @Nullable IBinder token, int userId, int sensorId,
+            @NonNull UserStoppedCallback callback) {
+        super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
+                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
+                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+        mUserStoppedCallback = callback;
+    }
+
+    @Override
+    public int getProtoEnum() {
+        return BiometricsProto.CM_STOP_USER;
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
new file mode 100644
index 0000000..c0ea2b3
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -0,0 +1,123 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.IBiometricService;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
+
+/**
+ * A user-aware scheduler that requests user-switches based on scheduled operation's targetUserId.
+ */
+public class UserAwareBiometricScheduler extends BiometricScheduler {
+
+    private static final String BASE_TAG = "UaBiometricScheduler";
+
+    /**
+     * Interface to retrieve the owner's notion of the current userId. Note that even though
+     * the scheduler can determine this based on its history of processed clients, we should still
+     * query the owner since it may be cleared due to things like HAL death, etc.
+     */
+    public interface CurrentUserRetriever {
+        int getCurrentUserId();
+    }
+
+    public interface UserSwitchCallback {
+        @NonNull StopUserClient<?> getStopUserClient(int userId);
+        @NonNull StartUserClient<?> getStartUserClient(int newUserId);
+    }
+
+    @NonNull private final CurrentUserRetriever mCurrentUserRetriever;
+    @NonNull private final UserSwitchCallback mUserSwitchCallback;
+    @NonNull @VisibleForTesting final ClientFinishedCallback mClientFinishedCallback;
+
+    @VisibleForTesting
+    class ClientFinishedCallback implements BaseClientMonitor.Callback {
+        @Override
+        public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
+            mHandler.post(() -> {
+                Slog.d(getTag(), "[Client finished] " + clientMonitor + ", success: " + success);
+
+                startNextOperationIfIdle();
+            });
+        }
+    }
+
+    @VisibleForTesting
+    UserAwareBiometricScheduler(@NonNull String tag,
+            @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull IBiometricService biometricService,
+            @NonNull CurrentUserRetriever currentUserRetriever,
+            @NonNull UserSwitchCallback userSwitchCallback) {
+        super(tag, gestureAvailabilityDispatcher, biometricService, LOG_NUM_RECENT_OPERATIONS);
+
+        mCurrentUserRetriever = currentUserRetriever;
+        mUserSwitchCallback = userSwitchCallback;
+        mClientFinishedCallback = new ClientFinishedCallback();
+    }
+
+    public UserAwareBiometricScheduler(@NonNull String tag,
+            @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull CurrentUserRetriever currentUserRetriever,
+            @NonNull UserSwitchCallback userSwitchCallback) {
+        this(tag, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface(
+                ServiceManager.getService(Context.BIOMETRIC_SERVICE)), currentUserRetriever,
+                userSwitchCallback);
+    }
+
+    @Override
+    protected String getTag() {
+        return BASE_TAG + "/" + mBiometricTag;
+    }
+
+    @Override
+    protected void startNextOperationIfIdle() {
+        if (mCurrentOperation != null) {
+            Slog.v(getTag(), "Not idle, current operation: " + mCurrentOperation);
+            return;
+        }
+        if (mPendingOperations.isEmpty()) {
+            Slog.d(getTag(), "No operations, returning to idle");
+            return;
+        }
+
+        final int currentUserId = mCurrentUserRetriever.getCurrentUserId();
+        final int nextUserId = mPendingOperations.getFirst().mClientMonitor.getTargetUserId();
+
+        if (nextUserId == currentUserId) {
+            super.startNextOperationIfIdle();
+        } else if (currentUserId == UserHandle.USER_NULL) {
+            Slog.d(getTag(), "User switch required, current user null, next: " + nextUserId);
+            final BaseClientMonitor startClient =
+                    mUserSwitchCallback.getStartUserClient(nextUserId);
+            startClient.start(mClientFinishedCallback);
+        } else {
+            final BaseClientMonitor stopClient = mUserSwitchCallback
+                    .getStopUserClient(currentUserId);
+            Slog.d(getTag(), "User switch required, current: " + currentUserId
+                    + ", next: " + nextUserId + ". " + stopClient);
+            stopClient.start(mClientFinishedCallback);
+        }
+    }
+}
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 ca29057..fdc3bb1 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
@@ -254,7 +254,7 @@
                         mContext.getOpPackageName(), sensorId,
                         mSensors.get(sensorId).getAuthenticatorIds());
 
-                mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+                scheduleForSensor(sensorId, client);
             } catch (RemoteException e) {
                 Slog.e(getTag(), "Remote exception when scheduling loadAuthenticatorId"
                         + ", sensorId: " + sensorId
@@ -268,7 +268,7 @@
             final InvalidationRequesterClient<Face> client =
                     new InvalidationRequesterClient<>(mContext, userId, sensorId,
                             FaceUtils.getInstance(sensorId));
-            mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+            scheduleForSensor(sensorId, client);
         });
     }
 
@@ -318,7 +318,7 @@
                 final FaceInvalidationClient client = new FaceInvalidationClient(mContext,
                         mSensors.get(sensorId).getLazySession(), userId, sensorId,
                         mSensors.get(sensorId).getAuthenticatorIds(), callback);
-                mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+                scheduleForSensor(sensorId, client);
             } catch (RemoteException e) {
                 Slog.e(getTag(), "Remote exception", e);
             }
@@ -468,7 +468,7 @@
                         false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
                         mUsageStats, mSensors.get(sensorId).getLockoutCache(),
                         allowBackgroundAuthentication);
-                mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+                scheduleForSensor(sensorId, client);
             } catch (RemoteException e) {
                 Slog.e(getTag(), "Remote exception when scheduling authenticate", e);
             }
@@ -523,7 +523,7 @@
                         opPackageName, FaceUtils.getInstance(sensorId), sensorId,
                         mSensors.get(sensorId).getAuthenticatorIds());
 
-                mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+                scheduleForSensor(sensorId, client);
             } catch (RemoteException e) {
                 Slog.e(getTag(), "Remote exception when scheduling remove", e);
             }
@@ -599,7 +599,7 @@
                                 FaceUtils.getInstance(sensorId),
                                 mSensors.get(sensorId).getAuthenticatorIds());
 
-                mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
+                scheduleForSensor(sensorId, client);
             } catch (RemoteException e) {
                 Slog.e(getTag(), "Remote exception when scheduling internal cleanup", e);
             }
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 c6f39aa..3eb4759 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
@@ -424,6 +424,12 @@
             });
         }
 
+        @Override
+        public void onSessionClosed() {
+            mHandler.post(() -> {
+              // TODO: implement this.
+            });
+        }
     }
 
     Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
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 1b5def6..bcca69b 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
@@ -291,7 +291,7 @@
                                 mSensors.get(sensorId).getLazySession(), userId,
                                 mContext.getOpPackageName(), sensorId,
                                 mSensors.get(sensorId).getAuthenticatorIds());
-                mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+                scheduleForSensor(sensorId, client);
             } catch (RemoteException e) {
                 Slog.e(getTag(), "Remote exception when scheduling loadAuthenticatorId"
                         + ", sensorId: " + sensorId
@@ -305,7 +305,7 @@
             final InvalidationRequesterClient<Fingerprint> client =
                     new InvalidationRequesterClient<>(mContext, userId, sensorId,
                             FingerprintUtils.getInstance(sensorId));
-            mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+            scheduleForSensor(sensorId, client);
         });
     }
 
@@ -458,7 +458,7 @@
                         mSensors.get(sensorId).getLazySession(), token, callback, userId,
                         opPackageName, sensorId, mUdfpsOverlayController, isStrongBiometric,
                         statsClient);
-                mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+                scheduleForSensor(sensorId, client);
             } catch (RemoteException e) {
                 Slog.e(getTag(), "Remote exception when scheduling finger detect", e);
             }
@@ -492,7 +492,7 @@
                         false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
                         mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
                         mUdfpsOverlayController, allowBackgroundAuthentication);
-                mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+                scheduleForSensor(sensorId, client);
             } catch (RemoteException e) {
                 Slog.e(getTag(), "Remote exception when scheduling authenticate", e);
             }
@@ -554,7 +554,7 @@
                         new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
                         opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
                         mSensors.get(sensorId).getAuthenticatorIds());
-                mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+                scheduleForSensor(sensorId, client);
             } catch (RemoteException e) {
                 Slog.e(getTag(), "Remote exception when scheduling remove", e);
             }
@@ -583,7 +583,7 @@
                                 mContext.getOpPackageName(), sensorId, enrolledList,
                                 FingerprintUtils.getInstance(sensorId),
                                 mSensors.get(sensorId).getAuthenticatorIds());
-                mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
+                scheduleForSensor(sensorId, client);
             } catch (RemoteException e) {
                 Slog.e(getTag(), "Remote exception when scheduling internal cleanup", e);
             }
@@ -627,7 +627,7 @@
                         new FingerprintInvalidationClient(mContext,
                                 mSensors.get(sensorId).getLazySession(), userId, sensorId,
                                 mSensors.get(sensorId).getAuthenticatorIds(), callback);
-                mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+                scheduleForSensor(sensorId, client);
             } catch (RemoteException e) {
                 Slog.e(getTag(), "Remote exception", e);
             }
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 5631647..d843bc9 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
@@ -403,6 +403,13 @@
                 invalidationClient.onAuthenticatorIdInvalidated(newAuthenticatorId);
             });
         }
+
+        @Override
+        public void onSessionClosed() {
+            mHandler.post(() -> {
+              // TODO: implement this.
+            });
+        }
     }
 
     Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 40d2073..00b39f1 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -38,7 +38,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserManager;
-import android.stats.camera.nano.CameraStreamProto;
+import android.stats.camera.nano.CameraProtos.CameraStreamProto;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 71565301..781bad7 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -106,16 +106,19 @@
         return bits;
     }
 
-    private void openPipe() {
+    private boolean openPipe() {
         try {
-            mPipe = new RandomAccessFile(PIPE_DEVICE, "rw");
-            mPipe.write(createOpenHandshake());
-        } catch (IOException e) {
+            final RandomAccessFile pipe = new RandomAccessFile(PIPE_DEVICE, "rw");
             try {
-                if (mPipe != null) mPipe.close();
-            } catch (IOException ee) {}
-            mPipe = null;
+                pipe.write(createOpenHandshake());
+                mPipe = pipe;
+                return true;
+            } catch (IOException ignore) {
+                pipe.close();
+            }
+        } catch (IOException ignore) {
         }
+        return false;
     }
 
     public HostClipboardMonitor(HostClipboardCallback cb) {
@@ -129,8 +132,7 @@
                 // There's no guarantee that QEMU pipes will be ready at the moment
                 // this method is invoked. We simply try to get the pipe open and
                 // retry on failure indefinitely.
-                while (mPipe == null) {
-                    openPipe();
+                while ((mPipe == null) && !openPipe()) {
                     Thread.sleep(100);
                 }
                 int size = mPipe.readInt();
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 74e4ae7..acf39f0 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -26,6 +26,7 @@
 import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
 import static android.net.SocketKeepalive.ERROR_INVALID_NETWORK;
 import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
+import static android.net.SocketKeepalive.ERROR_NO_SUCH_SLOT;
 import static android.net.SocketKeepalive.ERROR_STOP_REASON_UNINITIALIZED;
 import static android.net.SocketKeepalive.ERROR_UNSUPPORTED;
 import static android.net.SocketKeepalive.MAX_INTERVAL_SEC;
@@ -528,6 +529,8 @@
             }
         } else if (reason == ERROR_STOP_REASON_UNINITIALIZED) {
             throw new IllegalStateException("Unexpected stop reason: " + reason);
+        } else if (reason == ERROR_NO_SUCH_SLOT) {
+            throw new IllegalStateException("No such slot: " + reason);
         } else {
             notifyErrorCallback(ki.mCallback, reason);
         }
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 0c0d459..b57ad5d8 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -84,7 +84,7 @@
 
     // The context is for the current user (system server)
     private final Context mContext;
-    private final Resources mResources;
+    private final ConnectivityResources mResources;
     private final TelephonyManager mTelephonyManager;
     // The notification manager is created from a context for User.ALL, so notifications
     // will be sent to all users.
@@ -99,7 +99,7 @@
                 (NotificationManager) c.createContextAsUser(UserHandle.ALL, 0 /* flags */)
                         .getSystemService(Context.NOTIFICATION_SERVICE);
         mNotificationTypeMap = new SparseIntArray();
-        mResources = new ConnectivityResources(mContext).get();
+        mResources = new ConnectivityResources(mContext);
     }
 
     @VisibleForTesting
@@ -118,11 +118,11 @@
     }
 
     private String getTransportName(final int transportType) {
-        String[] networkTypes = mResources.getStringArray(R.array.network_switch_type_name);
+        String[] networkTypes = mResources.get().getStringArray(R.array.network_switch_type_name);
         try {
             return networkTypes[transportType];
         } catch (IndexOutOfBoundsException e) {
-            return mResources.getString(R.string.network_switch_type_name_unknown);
+            return mResources.get().getString(R.string.network_switch_type_name_unknown);
         }
     }
 
@@ -197,10 +197,11 @@
                     tag, nameOf(eventId), getTransportName(transportType), name, highPriority));
         }
 
-        final Resources r = mResources;
+        final Resources r = mResources.get();
         final CharSequence title;
         final CharSequence details;
-        Icon icon = Icon.createWithResource(r, getIcon(transportType));
+        Icon icon = Icon.createWithResource(
+                mResources.getResourcesContext(), getIcon(transportType));
         if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) {
             title = r.getString(R.string.wifi_no_internet, name);
             details = r.getString(R.string.wifi_no_internet_detailed);
@@ -355,7 +356,7 @@
     public void showToast(NetworkAgentInfo fromNai, NetworkAgentInfo toNai) {
         String fromTransport = getTransportName(approximateTransportType(fromNai));
         String toTransport = getTransportName(approximateTransportType(toNai));
-        String text = mResources.getString(
+        String text = mResources.get().getString(
                 R.string.network_switch_metered_toast, fromTransport, toTransport);
         Toast.makeText(mContext, text, Toast.LENGTH_LONG).show();
     }
diff --git a/services/core/java/com/android/server/connectivity/OsCompat.java b/services/core/java/com/android/server/connectivity/OsCompat.java
new file mode 100644
index 0000000..57e3dcd
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/OsCompat.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.server.connectivity;
+
+import android.system.ErrnoException;
+import android.system.Os;
+
+import java.io.FileDescriptor;
+
+/**
+ * Compatibility utility for android.system.Os core platform APIs.
+ *
+ * Connectivity has access to such APIs, but they are not part of the module_current stubs yet
+ * (only core_current). Most stable core platform APIs are included manually in the connectivity
+ * build rules, but because Os is also part of the base java SDK that is earlier on the
+ * classpath, the extra core platform APIs are not seen.
+ *
+ * TODO (b/157639992, b/183097033): remove as soon as core_current is part of system_server_current
+ * @hide
+ */
+public class OsCompat {
+    // This value should be correct on all architectures supported by Android, but hardcoding ioctl
+    // numbers should be avoided.
+    /**
+     * @see android.system.OsConstants#TIOCOUTQ
+     */
+    public static final int TIOCOUTQ = 0x5411;
+
+    /**
+     * @see android.system.Os#getsockoptInt(FileDescriptor, int, int)
+     */
+    public static int getsockoptInt(FileDescriptor fd, int level, int option) throws
+            ErrnoException {
+        try {
+            return (int) Os.class.getMethod(
+                    "getsockoptInt", FileDescriptor.class, int.class, int.class)
+                    .invoke(null, fd, level, option);
+        } catch (ReflectiveOperationException e) {
+            if (e.getCause() instanceof ErrnoException) {
+                throw (ErrnoException) e.getCause();
+            }
+            throw new IllegalStateException("Error calling getsockoptInt", e);
+        }
+    }
+
+    /**
+     * @see android.system.Os#ioctlInt(FileDescriptor, int)
+     */
+    public static int ioctlInt(FileDescriptor fd, int cmd) throws
+            ErrnoException {
+        try {
+            return (int) Os.class.getMethod(
+                    "ioctlInt", FileDescriptor.class, int.class).invoke(null, fd, cmd);
+        } catch (ReflectiveOperationException e) {
+            if (e.getCause() instanceof ErrnoException) {
+                throw (ErrnoException) e.getCause();
+            }
+            throw new IllegalStateException("Error calling ioctlInt", e);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
index c480594..73f3475 100644
--- a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
+++ b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
@@ -27,7 +27,8 @@
 import static android.system.OsConstants.IPPROTO_TCP;
 import static android.system.OsConstants.IP_TOS;
 import static android.system.OsConstants.IP_TTL;
-import static android.system.OsConstants.TIOCOUTQ;
+
+import static com.android.server.connectivity.OsCompat.TIOCOUTQ;
 
 import android.annotation.NonNull;
 import android.net.InvalidPacketException;
@@ -175,10 +176,10 @@
             }
             // Query write sequence number from SEND_QUEUE.
             Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_SEND_QUEUE);
-            tcpDetails.seq = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
+            tcpDetails.seq = OsCompat.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
             // Query read sequence number from RECV_QUEUE.
             Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_RECV_QUEUE);
-            tcpDetails.ack = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
+            tcpDetails.ack = OsCompat.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
             // Switch to NO_QUEUE to prevent illegal socket read/write in repair mode.
             Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_NO_QUEUE);
             // Finally, check if socket is still idle. TODO : this check needs to move to
@@ -198,9 +199,9 @@
             tcpDetails.rcvWndScale = trw.rcvWndScale;
             if (tcpDetails.srcAddress.length == 4 /* V4 address length */) {
                 // Query TOS.
-                tcpDetails.tos = Os.getsockoptInt(fd, IPPROTO_IP, IP_TOS);
+                tcpDetails.tos = OsCompat.getsockoptInt(fd, IPPROTO_IP, IP_TOS);
                 // Query TTL.
-                tcpDetails.ttl = Os.getsockoptInt(fd, IPPROTO_IP, IP_TTL);
+                tcpDetails.ttl = OsCompat.getsockoptInt(fd, IPPROTO_IP, IP_TTL);
             }
         } catch (ErrnoException e) {
             Log.e(TAG, "Exception reading TCP state from socket", e);
@@ -305,7 +306,7 @@
 
     private static boolean isReceiveQueueEmpty(FileDescriptor fd)
             throws ErrnoException {
-        final int result = Os.ioctlInt(fd, SIOCINQ);
+        final int result = OsCompat.ioctlInt(fd, SIOCINQ);
         if (result != 0) {
             Log.e(TAG, "Read queue has data");
             return false;
@@ -315,7 +316,7 @@
 
     private static boolean isSendQueueEmpty(FileDescriptor fd)
             throws ErrnoException {
-        final int result = Os.ioctlInt(fd, SIOCOUTQ);
+        final int result = OsCompat.ioctlInt(fd, SIOCOUTQ);
         if (result != 0) {
             Log.e(TAG, "Write queue has data");
             return false;
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 83866c6..70401b4 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -17,7 +17,6 @@
 package com.android.server.connectivity;
 
 import static android.Manifest.permission.BIND_VPN_SERVICE;
-import static android.net.ConnectivityManager.NETID_UNSET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.RouteInfo.RTN_THROW;
 import static android.net.RouteInfo.RTN_UNREACHABLE;
@@ -1129,17 +1128,17 @@
     }
 
     /**
-     * Return netId of current running VPN network.
+     * Return Network of current running VPN network.
      *
-     * @return a netId if there is a running VPN network or NETID_UNSET if there is no running VPN
+     * @return a Network if there is a running VPN network or null if there is no running VPN
      *         network or network is null.
      */
-    public synchronized int getNetId() {
+    public synchronized Network getNetwork() {
         final NetworkAgent agent = mNetworkAgent;
-        if (null == agent) return NETID_UNSET;
+        if (null == agent) return null;
         final Network network = agent.getNetwork();
-        if (null == network) return NETID_UNSET;
-        return network.getNetId();
+        if (null == network) return null;
+        return network;
     }
 
     private LinkProperties makeLinkProperties() {
@@ -1859,22 +1858,13 @@
     /**
      * Updates underlying network set.
      */
-    public synchronized boolean setUnderlyingNetworks(Network[] networks) {
+    public synchronized boolean setUnderlyingNetworks(@Nullable Network[] networks) {
         if (!isCallerEstablishedOwnerLocked()) {
             return false;
         }
-        if (networks == null) {
-            mConfig.underlyingNetworks = null;
-        } else {
-            mConfig.underlyingNetworks = new Network[networks.length];
-            for (int i = 0; i < networks.length; ++i) {
-                if (networks[i] == null) {
-                    mConfig.underlyingNetworks[i] = null;
-                } else {
-                    mConfig.underlyingNetworks[i] = new Network(networks[i].getNetId());
-                }
-            }
-        }
+        // Make defensive copy since the content of array might be altered by the caller.
+        mConfig.underlyingNetworks =
+                (networks != null) ? Arrays.copyOf(networks, networks.length) : null;
         mNetworkAgent.setUnderlyingNetworks((mConfig.underlyingNetworks != null)
                 ? Arrays.asList(mConfig.underlyingNetworks) : null);
         return true;
diff --git a/services/core/java/com/android/server/content/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java
index aaf9cbc..1f46061 100644
--- a/services/core/java/com/android/server/content/SyncJobService.java
+++ b/services/core/java/com/android/server/content/SyncJobService.java
@@ -119,7 +119,7 @@
     public boolean onStopJob(JobParameters params) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Slog.v(TAG, "onStopJob called " + params.getJobId() + ", reason: "
-                    + params.getStopReason());
+                    + params.getLegacyStopReason());
         }
         final SyncOperation op = SyncOperation.maybeCreateFromJobExtras(params.getExtras());
         if (op == null) {
@@ -161,9 +161,9 @@
         m.obj = op;
 
         // Reschedule if this job was NOT explicitly canceled.
-        m.arg1 = params.getStopReason() != JobParameters.REASON_CANCELED ? 1 : 0;
+        m.arg1 = params.getLegacyStopReason() != JobParameters.REASON_CANCELED ? 1 : 0;
         // Apply backoff only if stop is called due to timeout.
-        m.arg2 = params.getStopReason() == JobParameters.REASON_TIMEOUT ? 1 : 0;
+        m.arg2 = params.getLegacyStopReason() == JobParameters.REASON_TIMEOUT ? 1 : 0;
 
         SyncManager.sendMessage(m);
         return false;
@@ -204,7 +204,8 @@
             return "job:null";
         } else {
             return "job:#" + params.getJobId() + ":"
-                    + "sr=[" + params.getStopReason() + "/" + params.getDebugStopReason() + "]:"
+                    + "sr=[" + params.getLegacyStopReason()
+                    + "/" + params.getDebugStopReason() + "]:"
                     + SyncOperation.maybeCreateFromJobExtras(params.getExtras());
         }
     }
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 0925027..0f13741 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -76,6 +76,8 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.VibrationEffect;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
@@ -1917,9 +1919,9 @@
     }
 
     private static class VibrationInfo {
-        private long[] mPattern = new long[0];
-        private int[] mAmplitudes = new int[0];
-        private int mRepeat = -1;
+        private final long[] mPattern;
+        private final int[] mAmplitudes;
+        private final int mRepeat;
 
         public long[] getPattern() {
             return mPattern;
@@ -1934,40 +1936,55 @@
         }
 
         VibrationInfo(VibrationEffect effect) {
-            // First replace prebaked effects with its fallback, if any available.
-            if (effect instanceof VibrationEffect.Prebaked) {
-                VibrationEffect fallback = ((VibrationEffect.Prebaked) effect).getFallbackEffect();
-                if (fallback != null) {
-                    effect = fallback;
+            long[] pattern = null;
+            int[] amplitudes = null;
+            int patternRepeatIndex = -1;
+            int amplitudeCount = -1;
+
+            if (effect instanceof VibrationEffect.Composed) {
+                VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+                int segmentCount = composed.getSegments().size();
+                pattern = new long[segmentCount];
+                amplitudes = new int[segmentCount];
+                patternRepeatIndex = composed.getRepeatIndex();
+                amplitudeCount = 0;
+                for (int i = 0; i < segmentCount; i++) {
+                    VibrationEffectSegment segment = composed.getSegments().get(i);
+                    if (composed.getRepeatIndex() == i) {
+                        patternRepeatIndex = amplitudeCount;
+                    }
+                    if (!(segment instanceof StepSegment)) {
+                        Slog.w(TAG, "Input devices don't support segment " + segment);
+                        amplitudeCount = -1;
+                        break;
+                    }
+                    float amplitude = ((StepSegment) segment).getAmplitude();
+                    if (Float.compare(amplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0) {
+                        amplitudes[amplitudeCount] = DEFAULT_VIBRATION_MAGNITUDE;
+                    } else {
+                        amplitudes[amplitudeCount] =
+                                (int) (amplitude * VibrationEffect.MAX_AMPLITUDE);
+                    }
+                    pattern[amplitudeCount++] = segment.getDuration();
                 }
             }
-            if (effect instanceof VibrationEffect.OneShot) {
-                VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
-                mPattern = new long[] { 0, oneShot.getDuration() };
-                int amplitude = oneShot.getAmplitude();
-                // android framework uses DEFAULT_AMPLITUDE to signal that the vibration
-                // should use some built-in default value, denoted here as
-                // DEFAULT_VIBRATION_MAGNITUDE
-                if (amplitude == VibrationEffect.DEFAULT_AMPLITUDE) {
-                    amplitude = DEFAULT_VIBRATION_MAGNITUDE;
-                }
-                mAmplitudes = new int[] { 0, amplitude };
+
+            if (amplitudeCount < 0) {
+                Slog.w(TAG, "Only oneshot and step waveforms are supported on input devices");
+                mPattern = new long[0];
+                mAmplitudes = new int[0];
                 mRepeat = -1;
-            } else if (effect instanceof VibrationEffect.Waveform) {
-                VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
-                mPattern = waveform.getTimings();
-                mAmplitudes = waveform.getAmplitudes();
-                for (int i = 0; i < mAmplitudes.length; i++) {
-                    if (mAmplitudes[i] == VibrationEffect.DEFAULT_AMPLITUDE) {
-                        mAmplitudes[i] = DEFAULT_VIBRATION_MAGNITUDE;
-                    }
-                }
-                mRepeat = waveform.getRepeatIndex();
-                if (mRepeat >= mPattern.length) {
-                    throw new ArrayIndexOutOfBoundsException();
-                }
             } else {
-                Slog.w(TAG, "Pre-baked and composed effects aren't supported on input devices");
+                mRepeat = patternRepeatIndex;
+                mPattern = new long[amplitudeCount];
+                mAmplitudes = new int[amplitudeCount];
+                System.arraycopy(pattern, 0, mPattern, 0, amplitudeCount);
+                System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudeCount);
+                if (mRepeat >= mPattern.length) {
+                    throw new ArrayIndexOutOfBoundsException("Repeat index " + mRepeat
+                            + " must be within the bounds of the pattern.length "
+                            + mPattern.length);
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index ffb532e..c9364c6 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -17,6 +17,7 @@
 
 import static android.inputmethodservice.InputMethodService.FINISH_INPUT_NO_FALLBACK_CONNECTION;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
 import static android.os.IServiceManager.DUMP_FLAG_PROTO;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.server.inputmethod.InputMethodManagerServiceProto.ACCESSIBILITY_REQUESTING_NO_SOFT_KEYBOARD;
@@ -175,11 +176,9 @@
 import com.android.internal.inputmethod.UnbindReason;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
-import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.os.TransferPipe;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.view.IInlineSuggestionsRequestCallback;
 import com.android.internal.view.IInlineSuggestionsResponseCallback;
@@ -199,6 +198,7 @@
 import com.android.server.inputmethod.InputMethodUtils.InputMethodSettings;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.statusbar.StatusBarManagerService;
+import com.android.server.utils.PriorityDump;
 import com.android.server.wm.WindowManagerInternal;
 
 import java.io.FileDescriptor;
@@ -256,7 +256,6 @@
     static final int MSG_SET_ACTIVE = 3020;
     static final int MSG_SET_INTERACTIVE = 3030;
     static final int MSG_REPORT_FULLSCREEN_MODE = 3045;
-    static final int MSG_APPLY_IME_VISIBILITY = 3070;
 
     static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
 
@@ -1567,7 +1566,7 @@
             LocalServices.addService(InputMethodManagerInternal.class,
                     new LocalServiceImpl(mService));
             publishBinderService(Context.INPUT_METHOD_SERVICE, mService, false /*allowIsolated*/,
-                    DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);
+                    DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
         }
 
         @Override
@@ -3201,7 +3200,7 @@
     boolean showCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver,
             @SoftInputShowHideReason int reason) {
         mShowRequested = true;
-        if (mAccessibilityRequestingNoSoftKeyboard) {
+        if (mAccessibilityRequestingNoSoftKeyboard || mImeHiddenByDisplayPolicy) {
             return false;
         }
 
@@ -4102,7 +4101,6 @@
      */
     @BinderThread
     @Override
-    @GuardedBy("mMethodMap")
     public void startProtoDump(byte[] protoDump, int source, String where,
             IVoidResultCallback resultCallback) {
         CallbackUtils.onResult(resultCallback, () -> {
@@ -4199,7 +4197,6 @@
         });
     }
 
-    @GuardedBy("mMethodMap")
     private void dumpDebug(ProtoOutputStream proto, long fieldId) {
         synchronized (mMethodMap) {
             final long token = proto.start(fieldId);
@@ -4603,18 +4600,6 @@
                 }
                 return true;
             }
-            case MSG_APPLY_IME_VISIBILITY: {
-                final boolean setVisible = msg.arg1 != 0;
-                final ClientState clientState = (ClientState) msg.obj;
-                try {
-                    clientState.client.applyImeVisibility(setVisible);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Got RemoteException sending "
-                            + "applyImeVisibility(" + setVisible + ") notification to pid="
-                            + clientState.pid + " uid=" + clientState.uid);
-                }
-                return true;
-            }
 
             // --------------------------------------------------------------
             case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
@@ -5240,26 +5225,71 @@
         }
     }
 
+    private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
+        /**
+         * {@inheritDoc}
+         */
+        @BinderThread
+        @Override
+        public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
+                boolean asProto) {
+            if (asProto) {
+                dumpAsProtoNoCheck(fd);
+            } else {
+                dumpAsStringNoCheck(fd, pw, args, true /* isCritical */);
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @BinderThread
+        @Override
+        public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+            dumpNormal(fd, pw, args, asProto);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @BinderThread
+        @Override
+        public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+            if (asProto) {
+                dumpAsProtoNoCheck(fd);
+            } else {
+                dumpAsStringNoCheck(fd, pw, args, false /* isCritical */);
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @BinderThread
+        @Override
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+            dumpNormal(fd, pw, args, asProto);
+        }
+
+        @BinderThread
+        private void dumpAsProtoNoCheck(FileDescriptor fd) {
+            final ProtoOutputStream proto = new ProtoOutputStream(fd);
+            dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
+            proto.flush();
+        }
+    };
+
+    @BinderThread
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
-        if (ArrayUtils.contains(args, PROTO_ARG)) {
-            final ImeTracing imeTracing = ImeTracing.getInstance();
-            if (imeTracing.isEnabled()) {
-                imeTracing.stopTrace(null, false /* writeToFile */);
-                BackgroundThread.getHandler().post(() -> {
-                    imeTracing.writeTracesToFiles();
-                    imeTracing.startTrace(null);
-                });
-            }
+        PriorityDump.dump(mPriorityDumper, fd, pw, args);
+    }
 
-            final ProtoOutputStream proto = new ProtoOutputStream(fd);
-            dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
-            proto.flush();
-            return;
-        }
-
+    @BinderThread
+    private void dumpAsStringNoCheck(FileDescriptor fd, PrintWriter pw, String[] args,
+            boolean isCritical) {
         IInputMethod method;
         ClientState client;
         ClientState focusedWindowClient;
@@ -5323,6 +5353,11 @@
             mSoftInputShowHideHistory.dump(pw, "   ");
         }
 
+        // Exit here for critical dump, as remaining sections require IPCs to other processes.
+        if (isCritical) {
+            return;
+        }
+
         p.println(" ");
         if (client != null) {
             pw.flush();
@@ -5831,7 +5866,7 @@
     }
 
     /**
-     * Handles {@code adb shell ime tracing start/stop}.
+     * Handles {@code adb shell cmd input_method tracing start/stop/save-for-bugreport}.
      * @param shellCommand {@link ShellCommand} object that is handling this command.
      * @return Exit code of the command.
      */
@@ -5843,16 +5878,19 @@
         switch (cmd) {
             case "start":
                 ImeTracing.getInstance().getInstance().startTrace(pw);
-                break;
+                break;  // proceed to the next step to update the IME client processes.
             case "stop":
                 ImeTracing.getInstance().stopTrace(pw);
-                break;
+                break;  // proceed to the next step to update the IME client processes.
+            case "save-for-bugreport":
+                ImeTracing.getInstance().saveForBugreport(pw);
+                return ShellCommandResult.SUCCESS;  // no need to update the IME client processes.
             default:
                 pw.println("Unknown command: " + cmd);
                 pw.println("Input method trace options:");
                 pw.println("  start: Start tracing");
                 pw.println("  stop: Stop tracing");
-                return ShellCommandResult.FAILURE;
+                return ShellCommandResult.FAILURE;  // no need to update the IME client processes.
         }
         boolean isImeTraceEnabled = ImeTracing.getInstance().isEnabled();
         ArrayMap<IBinder, ClientState> clients;
diff --git a/services/core/java/com/android/server/location/GeocoderProxy.java b/services/core/java/com/android/server/location/GeocoderProxy.java
index c93c4b1..dc3596b 100644
--- a/services/core/java/com/android/server/location/GeocoderProxy.java
+++ b/services/core/java/com/android/server/location/GeocoderProxy.java
@@ -24,6 +24,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
 import com.android.server.servicewatcher.ServiceWatcher;
 
 import java.util.Collections;
@@ -54,9 +55,11 @@
     private final ServiceWatcher mServiceWatcher;
 
     private GeocoderProxy(Context context) {
-        mServiceWatcher = new ServiceWatcher(context, SERVICE_ACTION, null, null,
-                com.android.internal.R.bool.config_enableGeocoderOverlay,
-                com.android.internal.R.string.config_geocoderProviderPackageName);
+        mServiceWatcher = ServiceWatcher.create(context, "GeocoderProxy",
+                new CurrentUserServiceSupplier(context, SERVICE_ACTION,
+                        com.android.internal.R.bool.config_enableGeocoderOverlay,
+                        com.android.internal.R.string.config_geocoderProviderPackageName),
+                null);
     }
 
     private boolean register() {
@@ -72,7 +75,7 @@
      */
     public void getFromLocation(double latitude, double longitude, int maxResults,
             GeocoderParams params, IGeocodeListener listener) {
-        mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+        mServiceWatcher.runOnBinder(new ServiceWatcher.BinderOperation() {
             @Override
             public void run(IBinder binder) throws RemoteException {
                 IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder);
@@ -97,7 +100,7 @@
             double lowerLeftLatitude, double lowerLeftLongitude,
             double upperRightLatitude, double upperRightLongitude, int maxResults,
             GeocoderParams params, IGeocodeListener listener) {
-        mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+        mServiceWatcher.runOnBinder(new ServiceWatcher.BinderOperation() {
             @Override
             public void run(IBinder binder) throws RemoteException {
                 IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder);
diff --git a/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java
index e1c8700..6ac6e77 100644
--- a/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java
+++ b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java
@@ -25,15 +25,15 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier.BoundServiceInfo;
 import com.android.server.servicewatcher.ServiceWatcher;
-import com.android.server.servicewatcher.ServiceWatcher.BoundService;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceListener;
 
 /**
  * Proxy class to bind GmsCore to the ActivityRecognitionHardware.
- *
- * @hide
  */
-public class HardwareActivityRecognitionProxy {
+public class HardwareActivityRecognitionProxy implements ServiceListener<BoundServiceInfo> {
 
     private static final String TAG = "ARProxy";
     private static final String SERVICE_ACTION =
@@ -66,12 +66,16 @@
             mInstance = null;
         }
 
-        mServiceWatcher = new ServiceWatcher(context,
-                SERVICE_ACTION,
-                this::onBind,
-                null,
-                com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay,
-                com.android.internal.R.string.config_activityRecognitionHardwarePackageName);
+        int useOverlayResId =
+                com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay;
+        int nonOverlayPackageResId =
+                com.android.internal.R.string.config_activityRecognitionHardwarePackageName;
+
+        mServiceWatcher = ServiceWatcher.create(context,
+                "HardwareActivityRecognitionProxy",
+                new CurrentUserServiceSupplier(context, SERVICE_ACTION, useOverlayResId,
+                        nonOverlayPackageResId),
+                this);
     }
 
     private boolean register() {
@@ -82,7 +86,8 @@
         return resolves;
     }
 
-    private void onBind(IBinder binder, BoundService service) throws RemoteException {
+    @Override
+    public void onBind(IBinder binder, BoundServiceInfo boundServiceInfo) throws RemoteException {
         String descriptor = binder.getInterfaceDescriptor();
 
         if (IActivityRecognitionHardwareWatcher.class.getCanonicalName().equals(descriptor)) {
@@ -99,4 +104,7 @@
             Log.e(TAG, "Unknown descriptor: " + descriptor);
         }
     }
+
+    @Override
+    public void onUnbind() {}
 }
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 2920ddb..864aa33 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -31,6 +31,7 @@
 
 import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
 import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
+import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
 
 import static java.util.concurrent.TimeUnit.NANOSECONDS;
 
@@ -158,10 +159,9 @@
 
         public Lifecycle(Context context) {
             super(context);
-            LocationEventLog eventLog = new LocationEventLog();
             mUserInfoHelper = new LifecycleUserInfoHelper(context);
-            mSystemInjector = new SystemInjector(context, mUserInfoHelper, eventLog);
-            mService = new LocationManagerService(context, mSystemInjector, eventLog);
+            mSystemInjector = new SystemInjector(context, mUserInfoHelper);
+            mService = new LocationManagerService(context, mSystemInjector);
         }
 
         @Override
@@ -233,7 +233,6 @@
 
     private final Context mContext;
     private final Injector mInjector;
-    private final LocationEventLog mEventLog;
     private final LocalService mLocalService;
 
     private final GeofenceManager mGeofenceManager;
@@ -261,18 +260,20 @@
     @GuardedBy("mLock")
     private @Nullable OnProviderLocationTagsChangeListener mOnProviderLocationTagsChangeListener;
 
-    LocationManagerService(Context context, Injector injector, LocationEventLog eventLog) {
+    LocationManagerService(Context context, Injector injector) {
         mContext = context.createAttributionContext(ATTRIBUTION_TAG);
         mInjector = injector;
-        mEventLog = eventLog;
         mLocalService = new LocalService();
         LocalServices.addService(LocationManagerInternal.class, mLocalService);
 
         mGeofenceManager = new GeofenceManager(mContext, injector);
 
+        mInjector.getSettingsHelper().addOnLocationEnabledChangedListener(
+                this::onLocationModeChanged);
+
         // set up passive provider first since it will be required for all other location providers,
         // which are loaded later once the system is ready.
-        mPassiveManager = new PassiveLocationProviderManager(mContext, injector, mEventLog);
+        mPassiveManager = new PassiveLocationProviderManager(mContext, injector);
         addLocationProviderManager(mPassiveManager, new PassiveLocationProvider(mContext));
 
         // TODO: load the gps provider here as well, which will require refactoring
@@ -313,7 +314,7 @@
             }
 
             LocationProviderManager manager = new LocationProviderManager(mContext, mInjector,
-                    mEventLog, providerName, mPassiveManager);
+                    providerName, mPassiveManager);
             addLocationProviderManager(manager, null);
             return manager;
         }
@@ -335,7 +336,7 @@
                             Settings.Global.LOCATION_ENABLE_STATIONARY_THROTTLE, 1) != 0;
                     if (enableStationaryThrottling) {
                         realProvider = new StationaryThrottlingLocationProvider(manager.getName(),
-                                mInjector, realProvider, mEventLog);
+                                mInjector, realProvider);
                     }
                 }
                 manager.setRealProvider(realProvider);
@@ -355,9 +356,6 @@
     }
 
     void onSystemReady() {
-        mInjector.getSettingsHelper().addOnLocationEnabledChangedListener(
-                this::onLocationModeChanged);
-
         if (Build.IS_DEBUGGABLE) {
             // on debug builds, watch for location noteOps while location is off. there are some
             // scenarios (emergency location) where this is expected, but generally this should
@@ -380,12 +378,13 @@
         // provider has unfortunate hard dependencies on the network provider
         ProxyLocationProvider networkProvider = ProxyLocationProvider.create(
                 mContext,
+                NETWORK_PROVIDER,
                 ACTION_NETWORK_PROVIDER,
                 com.android.internal.R.bool.config_enableNetworkLocationOverlay,
                 com.android.internal.R.string.config_networkLocationProviderPackageName);
         if (networkProvider != null) {
             LocationProviderManager networkManager = new LocationProviderManager(mContext,
-                    mInjector, mEventLog, NETWORK_PROVIDER, mPassiveManager);
+                    mInjector, NETWORK_PROVIDER, mPassiveManager);
             addLocationProviderManager(networkManager, networkProvider);
         } else {
             Log.w(TAG, "no network location provider found");
@@ -399,12 +398,13 @@
 
         ProxyLocationProvider fusedProvider = ProxyLocationProvider.create(
                 mContext,
+                FUSED_PROVIDER,
                 ACTION_FUSED_PROVIDER,
                 com.android.internal.R.bool.config_enableFusedLocationOverlay,
                 com.android.internal.R.string.config_fusedLocationProviderPackageName);
         if (fusedProvider != null) {
             LocationProviderManager fusedManager = new LocationProviderManager(mContext, mInjector,
-                    mEventLog, FUSED_PROVIDER, mPassiveManager);
+                    FUSED_PROVIDER, mPassiveManager);
             addLocationProviderManager(fusedManager, fusedProvider);
         } else {
             Log.wtf(TAG, "no fused location provider found");
@@ -419,7 +419,7 @@
             mGnssManagerService.onSystemReady();
 
             LocationProviderManager gnssManager = new LocationProviderManager(mContext, mInjector,
-                    mEventLog, GPS_PROVIDER, mPassiveManager);
+                    GPS_PROVIDER, mPassiveManager);
             addLocationProviderManager(gnssManager, mGnssManagerService.getGnssLocationProvider());
         }
 
@@ -476,7 +476,7 @@
             Log.d(TAG, "[u" + userId + "] location enabled = " + enabled);
         }
 
-        mEventLog.logLocationEnabled(userId, enabled);
+        EVENT_LOG.logLocationEnabled(userId, enabled);
 
         Intent intent = new Intent(LocationManager.MODE_CHANGED_ACTION)
                 .putExtra(LocationManager.EXTRA_LOCATION_ENABLED, enabled)
@@ -1268,7 +1268,7 @@
 
                 ipw.println("Event Log:");
                 ipw.increaseIndent();
-                mEventLog.iterate(manager.getName(), ipw::println);
+                EVENT_LOG.iterate(manager.getName(), ipw::println);
                 ipw.decreaseIndent();
                 return;
             }
@@ -1313,7 +1313,7 @@
         ipw.println("Historical Aggregate Location Provider Data:");
         ipw.increaseIndent();
         ArrayMap<String, ArrayMap<CallerIdentity, LocationEventLog.AggregateStats>> aggregateStats =
-                mEventLog.copyAggregateStats();
+                EVENT_LOG.copyAggregateStats();
         for (int i = 0; i < aggregateStats.size(); i++) {
             ipw.print(aggregateStats.keyAt(i));
             ipw.println(":");
@@ -1344,7 +1344,7 @@
 
         ipw.println("Event Log:");
         ipw.increaseIndent();
-        mEventLog.iterate(ipw::println);
+        EVENT_LOG.iterate(ipw::println);
         ipw.decreaseIndent();
     }
 
@@ -1456,7 +1456,7 @@
         @GuardedBy("this")
         private boolean mSystemReady;
 
-        SystemInjector(Context context, UserInfoHelper userInfoHelper, LocationEventLog eventLog) {
+        SystemInjector(Context context, UserInfoHelper userInfoHelper) {
             mContext = context;
 
             mUserInfoHelper = userInfoHelper;
@@ -1466,7 +1466,7 @@
                     mAppOpsHelper);
             mSettingsHelper = new SystemSettingsHelper(context);
             mAppForegroundHelper = new SystemAppForegroundHelper(context);
-            mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context, eventLog);
+            mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context);
             mScreenInteractiveHelper = new SystemScreenInteractiveHelper(context);
             mDeviceStationaryHelper = new SystemDeviceStationaryHelper();
             mDeviceIdleHelper = new SystemDeviceIdleHelper(context);
diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
index 29ce378..045e06d0 100644
--- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
@@ -44,6 +44,8 @@
 /** In memory event log for location events. */
 public class LocationEventLog extends LocalEventLog {
 
+    public static final LocationEventLog EVENT_LOG = new LocationEventLog();
+
     private static int getLogSize() {
         if (Build.IS_DEBUGGABLE || D) {
             return 500;
@@ -52,16 +54,17 @@
         }
     }
 
-    private static final int EVENT_LOCATION_ENABLED = 1;
-    private static final int EVENT_PROVIDER_ENABLED = 2;
-    private static final int EVENT_PROVIDER_MOCKED = 3;
-    private static final int EVENT_PROVIDER_REGISTER_CLIENT = 4;
-    private static final int EVENT_PROVIDER_UNREGISTER_CLIENT = 5;
-    private static final int EVENT_PROVIDER_UPDATE_REQUEST = 6;
-    private static final int EVENT_PROVIDER_RECEIVE_LOCATION = 7;
-    private static final int EVENT_PROVIDER_DELIVER_LOCATION = 8;
-    private static final int EVENT_PROVIDER_STATIONARY_THROTTLED = 9;
-    private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 10;
+    private static final int EVENT_USER_SWITCHED = 1;
+    private static final int EVENT_LOCATION_ENABLED = 2;
+    private static final int EVENT_PROVIDER_ENABLED = 3;
+    private static final int EVENT_PROVIDER_MOCKED = 4;
+    private static final int EVENT_PROVIDER_REGISTER_CLIENT = 5;
+    private static final int EVENT_PROVIDER_UNREGISTER_CLIENT = 6;
+    private static final int EVENT_PROVIDER_UPDATE_REQUEST = 7;
+    private static final int EVENT_PROVIDER_RECEIVE_LOCATION = 8;
+    private static final int EVENT_PROVIDER_DELIVER_LOCATION = 9;
+    private static final int EVENT_PROVIDER_STATIONARY_THROTTLED = 10;
+    private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 11;
 
     @GuardedBy("mAggregateStats")
     private final ArrayMap<String, ArrayMap<CallerIdentity, AggregateStats>> mAggregateStats;
@@ -90,19 +93,24 @@
                 packageMap = new ArrayMap<>(2);
                 mAggregateStats.put(provider, packageMap);
             }
-            CallerIdentity stripped = identity.stripListenerId();
-            AggregateStats stats = packageMap.get(stripped);
+            CallerIdentity aggregate = CallerIdentity.forAggregation(identity);
+            AggregateStats stats = packageMap.get(aggregate);
             if (stats == null) {
                 stats = new AggregateStats();
-                packageMap.put(stripped, stats);
+                packageMap.put(aggregate, stats);
             }
             return stats;
         }
     }
 
+    /** Logs a user switched event. */
+    public void logUserSwitched(int userIdFrom, int userIdTo) {
+        addLogEvent(EVENT_USER_SWITCHED, userIdFrom, userIdTo);
+    }
+
     /** Logs a location enabled/disabled event. */
     public void logLocationEnabled(int userId, boolean enabled) {
-        addLogEvent(EVENT_LOCATION_POWER_SAVE_MODE_CHANGE, userId, enabled);
+        addLogEvent(EVENT_LOCATION_ENABLED, userId, enabled);
     }
 
     /** Logs a location provider enabled/disabled event. */
@@ -183,8 +191,10 @@
     @Override
     protected LogEvent createLogEvent(long timeDelta, int event, Object... args) {
         switch (event) {
+            case EVENT_USER_SWITCHED:
+                return new UserSwitchedEvent(timeDelta, (Integer) args[0], (Integer) args[1]);
             case EVENT_LOCATION_ENABLED:
-                return new LocationEnabledEvent(timeDelta, (Integer) args[1], (Boolean) args[2]);
+                return new LocationEnabledEvent(timeDelta, (Integer) args[0], (Boolean) args[1]);
             case EVENT_PROVIDER_ENABLED:
                 return new ProviderEnabledEvent(timeDelta, (String) args[0], (Integer) args[1],
                         (Boolean) args[2]);
@@ -397,6 +407,23 @@
         }
     }
 
+    private static final class UserSwitchedEvent extends LogEvent {
+
+        private final int mUserIdFrom;
+        private final int mUserIdTo;
+
+        UserSwitchedEvent(long timeDelta, int userIdFrom, int userIdTo) {
+            super(timeDelta);
+            mUserIdFrom = userIdFrom;
+            mUserIdTo = userIdTo;
+        }
+
+        @Override
+        public String getLogString() {
+            return "current user switched from u" + mUserIdFrom + " to u" + mUserIdTo;
+        }
+    }
+
     private static final class LocationEnabledEvent extends LogEvent {
 
         private final int mUserId;
@@ -410,7 +437,7 @@
 
         @Override
         public String getLogString() {
-            return "[u" + mUserId + "] location setting " + (mEnabled ? "enabled" : "disabled");
+            return "location [u" + mUserId + "] " + (mEnabled ? "enabled" : "disabled");
         }
     }
 
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceProxy.java b/services/core/java/com/android/server/location/geofence/GeofenceProxy.java
index c707149..90b446e 100644
--- a/services/core/java/com/android/server/location/geofence/GeofenceProxy.java
+++ b/services/core/java/com/android/server/location/geofence/GeofenceProxy.java
@@ -29,14 +29,17 @@
 import android.os.UserHandle;
 import android.util.Log;
 
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier.BoundServiceInfo;
 import com.android.server.servicewatcher.ServiceWatcher;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceListener;
 
 import java.util.Objects;
 
 /**
  * @hide
  */
-public final class GeofenceProxy {
+public final class GeofenceProxy implements ServiceListener<BoundServiceInfo> {
 
     private static final String TAG = "GeofenceProxy";
     private static final String SERVICE_ACTION = "com.android.location.service.GeofenceProvider";
@@ -62,10 +65,12 @@
 
     private GeofenceProxy(Context context, IGpsGeofenceHardware gpsGeofence) {
         mGpsGeofenceHardware = Objects.requireNonNull(gpsGeofence);
-        mServiceWatcher = new ServiceWatcher(context, SERVICE_ACTION,
-                (binder, service) -> updateGeofenceHardware(binder), null,
-                com.android.internal.R.bool.config_enableGeofenceOverlay,
-                com.android.internal.R.string.config_geofenceProviderPackageName);
+        mServiceWatcher = ServiceWatcher.create(context,
+                "GeofenceProxy",
+                new CurrentUserServiceSupplier(context, SERVICE_ACTION,
+                        com.android.internal.R.bool.config_enableGeofenceOverlay,
+                        com.android.internal.R.string.config_geofenceProviderPackageName),
+                this);
 
         mGeofenceHardware = null;
     }
@@ -87,6 +92,14 @@
         return resolves;
     }
 
+    @Override
+    public void onBind(IBinder binder, BoundServiceInfo boundServiceInfo) throws RemoteException {
+        updateGeofenceHardware(binder);
+    }
+
+    @Override
+    public void onUnbind() {}
+
     private class GeofenceProxyServiceConnection implements ServiceConnection {
 
         GeofenceProxyServiceConnection() {}
diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
index bcdfed4..21946ca 100644
--- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java
+++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
@@ -81,7 +81,7 @@
         mGnssNative = gnssNative;
 
         mGnssMetrics = new GnssMetrics(mContext, IBatteryStats.Stub.asInterface(
-                ServiceManager.getService(BatteryStats.SERVICE_NAME)));
+                ServiceManager.getService(BatteryStats.SERVICE_NAME)), mGnssNative);
 
         mGnssLocationProvider = new GnssLocationProvider(mContext, injector, mGnssNative,
                 mGnssMetrics);
diff --git a/services/core/java/com/android/server/location/gnss/GnssMetrics.java b/services/core/java/com/android/server/location/gnss/GnssMetrics.java
index c7d8144..dbc903d 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMetrics.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMetrics.java
@@ -35,6 +35,7 @@
 import com.android.internal.location.nano.GnssLogsProto.PowerMetrics;
 import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.location.gnss.hal.GnssNative;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -52,11 +53,14 @@
 
     /** Default time between location fixes (in millisecs) */
     private static final int DEFAULT_TIME_BETWEEN_FIXES_MILLISECS = 1000;
+    private static final int CONVERT_MILLI_TO_MICRO = 1000;
+    private static final int VENDOR_SPECIFIC_POWER_MODES_SIZE = 10;
 
     /** Frequency range of GPS L5, Galileo E5a, QZSS J5 frequency band */
     private static final double L5_CARRIER_FREQ_RANGE_LOW_HZ = 1164 * 1e6;
     private static final double L5_CARRIER_FREQ_RANGE_HIGH_HZ = 1189 * 1e6;
 
+
     private long mLogStartInElapsedRealtimeMs;
 
     GnssPowerMetrics mGnssPowerMetrics;
@@ -88,8 +92,10 @@
     long mL5SvStatusReportsUsedInFix;
 
     private final StatsManager mStatsManager;
+    private final GnssNative mGnssNative;
 
-    public GnssMetrics(Context context, IBatteryStats stats) {
+    public GnssMetrics(Context context, IBatteryStats stats, GnssNative gnssNative) {
+        mGnssNative = gnssNative;
         mGnssPowerMetrics = new GnssPowerMetrics(stats);
         mLocationFailureStatistics = new Statistics();
         mTimeToFirstFixSecStatistics = new Statistics();
@@ -189,8 +195,8 @@
     }
 
     /**
-    * Logs sv status data
-    */
+     * Logs sv status data
+     */
     public void logSvStatus(GnssStatus status) {
         boolean isL5;
         // Calculate SvStatus Information
@@ -216,8 +222,8 @@
     }
 
     /**
-    * Logs CN0 when at least 4 SVs are available L5 Only
-    */
+     * Logs CN0 when at least 4 SVs are available L5 Only
+     */
     private void logCn0L5(GnssStatus gnssStatus) {
         if (gnssStatus.getSatelliteCount() == 0) {
             return;
@@ -432,7 +438,8 @@
         private double mSumSquare;
         private long mLongSum;
 
-        Statistics() {}
+        Statistics() {
+        }
 
         /** Resets statistics */
         public synchronized void reset() {
@@ -498,7 +505,7 @@
         GnssPowerMetrics(IBatteryStats stats) {
             mBatteryStats = stats;
             // Used to initialize the variable to a very small value (unachievable in practice)
-          // so that
+            // so that
             // the first CNO report will trigger an update to BatteryStats
             mLastAverageCn0 = -100.0;
             mLastSignalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN;
@@ -585,6 +592,10 @@
                 FrameworkStatsLog.GNSS_STATS,
                 null, // use default PullAtomMetadata values
                 ConcurrentUtils.DIRECT_EXECUTOR, pullAtomCallback);
+        mStatsManager.setPullAtomCallback(
+                FrameworkStatsLog.GNSS_POWER_STATS,
+                null, // use default PullAtomMetadata values
+                ConcurrentUtils.DIRECT_EXECUTOR, pullAtomCallback);
     }
 
     /**
@@ -593,26 +604,68 @@
      */
     private class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback {
 
-        StatsPullAtomCallbackImpl() {}
+        StatsPullAtomCallbackImpl() {
+        }
 
         @Override
         public int onPullAtom(int atomTag, List<StatsEvent> data) {
-            if (atomTag != FrameworkStatsLog.GNSS_STATS) {
+            if (atomTag == FrameworkStatsLog.GNSS_STATS) {
+                data.add(FrameworkStatsLog.buildStatsEvent(atomTag,
+                        mLocationFailureReportsStatistics.getCount(),
+                        mLocationFailureReportsStatistics.getLongSum(),
+                        mTimeToFirstFixMilliSReportsStatistics.getCount(),
+                        mTimeToFirstFixMilliSReportsStatistics.getLongSum(),
+                        mPositionAccuracyMetersReportsStatistics.getCount(),
+                        mPositionAccuracyMetersReportsStatistics.getLongSum(),
+                        mTopFourAverageCn0DbmHzReportsStatistics.getCount(),
+                        mTopFourAverageCn0DbmHzReportsStatistics.getLongSum(),
+                        mL5TopFourAverageCn0DbmHzReportsStatistics.getCount(),
+                        mL5TopFourAverageCn0DbmHzReportsStatistics.getLongSum(), mSvStatusReports,
+                        mSvStatusReportsUsedInFix, mL5SvStatusReports,
+                        mL5SvStatusReportsUsedInFix));
+            } else if (atomTag == FrameworkStatsLog.GNSS_POWER_STATS) {
+                mGnssNative.requestPowerStats();
+                GnssPowerStats gnssPowerStats = mGnssNative.getPowerStats();
+                if (gnssPowerStats == null) {
+                    return StatsManager.PULL_SKIP;
+                }
+                double[] otherModesEnergyMilliJoule = new double[VENDOR_SPECIFIC_POWER_MODES_SIZE];
+                double[] tempGnssPowerStatsOtherModes =
+                        gnssPowerStats.getOtherModesEnergyMilliJoule();
+                if (tempGnssPowerStatsOtherModes.length < VENDOR_SPECIFIC_POWER_MODES_SIZE) {
+                    System.arraycopy(tempGnssPowerStatsOtherModes, 0,
+                            otherModesEnergyMilliJoule, 0,
+                            tempGnssPowerStatsOtherModes.length);
+                } else {
+                    System.arraycopy(tempGnssPowerStatsOtherModes, 0,
+                            otherModesEnergyMilliJoule, 0,
+                            VENDOR_SPECIFIC_POWER_MODES_SIZE);
+                }
+                data.add(FrameworkStatsLog.buildStatsEvent(atomTag,
+                        (long) (gnssPowerStats.getElapsedRealtimeUncertaintyNanos()),
+                        (long) (gnssPowerStats.getTotalEnergyMilliJoule() * CONVERT_MILLI_TO_MICRO),
+                        (long) (gnssPowerStats.getSinglebandTrackingModeEnergyMilliJoule()
+                                * CONVERT_MILLI_TO_MICRO),
+                        (long) (gnssPowerStats.getMultibandTrackingModeEnergyMilliJoule()
+                                * CONVERT_MILLI_TO_MICRO),
+                        (long) (gnssPowerStats.getSinglebandAcquisitionModeEnergyMilliJoule()
+                                * CONVERT_MILLI_TO_MICRO),
+                        (long) (gnssPowerStats.getMultibandAcquisitionModeEnergyMilliJoule()
+                                * CONVERT_MILLI_TO_MICRO),
+                        (long) (otherModesEnergyMilliJoule[0] * CONVERT_MILLI_TO_MICRO),
+                        (long) (otherModesEnergyMilliJoule[1] * CONVERT_MILLI_TO_MICRO),
+                        (long) (otherModesEnergyMilliJoule[2] * CONVERT_MILLI_TO_MICRO),
+                        (long) (otherModesEnergyMilliJoule[3] * CONVERT_MILLI_TO_MICRO),
+                        (long) (otherModesEnergyMilliJoule[4] * CONVERT_MILLI_TO_MICRO),
+                        (long) (otherModesEnergyMilliJoule[5] * CONVERT_MILLI_TO_MICRO),
+                        (long) (otherModesEnergyMilliJoule[6] * CONVERT_MILLI_TO_MICRO),
+                        (long) (otherModesEnergyMilliJoule[7] * CONVERT_MILLI_TO_MICRO),
+                        (long) (otherModesEnergyMilliJoule[8] * CONVERT_MILLI_TO_MICRO),
+                        (long) (otherModesEnergyMilliJoule[9] * CONVERT_MILLI_TO_MICRO)));
+            } else {
                 throw new UnsupportedOperationException("Unknown tagId = " + atomTag);
             }
-            data.add(FrameworkStatsLog.buildStatsEvent(atomTag,
-                    mLocationFailureReportsStatistics.getCount(),
-                    mLocationFailureReportsStatistics.getLongSum(),
-                    mTimeToFirstFixMilliSReportsStatistics.getCount(),
-                    mTimeToFirstFixMilliSReportsStatistics.getLongSum(),
-                    mPositionAccuracyMetersReportsStatistics.getCount(),
-                    mPositionAccuracyMetersReportsStatistics.getLongSum(),
-                    mTopFourAverageCn0DbmHzReportsStatistics.getCount(),
-                    mTopFourAverageCn0DbmHzReportsStatistics.getLongSum(),
-                    mL5TopFourAverageCn0DbmHzReportsStatistics.getCount(),
-                    mL5TopFourAverageCn0DbmHzReportsStatistics.getLongSum(), mSvStatusReports,
-                    mSvStatusReportsUsedInFix, mL5SvStatusReports, mL5SvStatusReportsUsedInFix));
             return StatsManager.PULL_SUCCESS;
         }
     }
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java
index cc00d56..53407d9 100644
--- a/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java
+++ b/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java
@@ -20,12 +20,11 @@
 
 import static com.android.server.location.LocationManagerService.D;
 import static com.android.server.location.LocationManagerService.TAG;
+import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
 
 import android.os.PowerManager.LocationPowerSaveMode;
 import android.util.Log;
 
-import com.android.server.location.eventlog.LocationEventLog;
-
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
@@ -43,11 +42,9 @@
         void onLocationPowerSaveModeChanged(@LocationPowerSaveMode int locationPowerSaveMode);
     }
 
-    private final LocationEventLog mLocationEventLog;
     private final CopyOnWriteArrayList<LocationPowerSaveModeChangedListener> mListeners;
 
-    public LocationPowerSaveModeHelper(LocationEventLog locationEventLog) {
-        mLocationEventLog = locationEventLog;
+    public LocationPowerSaveModeHelper() {
         mListeners = new CopyOnWriteArrayList<>();
     }
 
@@ -72,7 +69,7 @@
             Log.d(TAG, "location power save mode is now " + locationPowerSaveModeToString(
                     locationPowerSaveMode));
         }
-        mLocationEventLog.logLocationPowerSaveMode(locationPowerSaveMode);
+        EVENT_LOG.logLocationPowerSaveMode(locationPowerSaveMode);
 
         for (LocationPowerSaveModeChangedListener listener : mListeners) {
             listener.onLocationPowerSaveModeChanged(locationPowerSaveMode);
diff --git a/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java
index c47a64d..a675f54 100644
--- a/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java
@@ -25,7 +25,6 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
-import com.android.server.location.eventlog.LocationEventLog;
 
 import java.util.Objects;
 import java.util.function.Consumer;
@@ -42,8 +41,7 @@
     @LocationPowerSaveMode
     private volatile int mLocationPowerSaveMode;
 
-    public SystemLocationPowerSaveModeHelper(Context context, LocationEventLog locationEventLog) {
-        super(locationEventLog);
+    public SystemLocationPowerSaveModeHelper(Context context) {
         mContext = context;
     }
 
diff --git a/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java b/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
index 3f7345e..632ed6e 100644
--- a/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
@@ -35,6 +35,7 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Handler;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -348,31 +349,73 @@
      */
     @Override
     public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
-        int userId = ActivityManager.getCurrentUser();
+        int[] userIds;
+        try {
+            userIds = ActivityManager.getService().getRunningUserIds();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
 
-        ipw.print("Location Enabled: ");
-        ipw.println(isLocationEnabled(userId));
-
-        List<String> locationPackageBlacklist = mLocationPackageBlacklist.getValueForUser(userId);
-        if (!locationPackageBlacklist.isEmpty()) {
-            ipw.println("Location Deny Packages:");
-            ipw.increaseIndent();
-            for (String packageName : locationPackageBlacklist) {
-                ipw.println(packageName);
+        ipw.print("Location Setting: ");
+        ipw.increaseIndent();
+        if (userIds.length > 1) {
+            ipw.println();
+            for (int userId : userIds) {
+                ipw.print("[u");
+                ipw.print(userId);
+                ipw.print("] ");
+                ipw.println(isLocationEnabled(userId));
             }
-            ipw.decreaseIndent();
+        } else {
+            ipw.println(isLocationEnabled(userIds[0]));
+        }
+        ipw.decreaseIndent();
 
-            List<String> locationPackageWhitelist = mLocationPackageWhitelist.getValueForUser(
-                    userId);
-            if (!locationPackageWhitelist.isEmpty()) {
-                ipw.println("Location Allow Packages:");
+        ipw.println("Location Allow/Deny Packages:");
+        ipw.increaseIndent();
+        if (userIds.length > 1) {
+            for (int userId : userIds) {
+                List<String> locationPackageBlacklist = mLocationPackageBlacklist.getValueForUser(
+                        userId);
+                if (locationPackageBlacklist.isEmpty()) {
+                    continue;
+                }
+
+                ipw.print("user ");
+                ipw.print(userId);
+                ipw.println(":");
                 ipw.increaseIndent();
-                for (String packageName : locationPackageWhitelist) {
+
+                for (String packageName : locationPackageBlacklist) {
+                    ipw.print("[deny] ");
                     ipw.println(packageName);
                 }
+
+                List<String> locationPackageWhitelist = mLocationPackageWhitelist.getValueForUser(
+                        userId);
+                for (String packageName : locationPackageWhitelist) {
+                    ipw.print("[allow] ");
+                    ipw.println(packageName);
+                }
+
                 ipw.decreaseIndent();
             }
+        } else {
+            List<String> locationPackageBlacklist = mLocationPackageBlacklist.getValueForUser(
+                    userIds[0]);
+            for (String packageName : locationPackageBlacklist) {
+                ipw.print("[deny] ");
+                ipw.println(packageName);
+            }
+
+            List<String> locationPackageWhitelist = mLocationPackageWhitelist.getValueForUser(
+                    userIds[0]);
+            for (String packageName : locationPackageWhitelist) {
+                ipw.print("[allow] ");
+                ipw.println(packageName);
+            }
         }
+        ipw.decreaseIndent();
 
         Set<String> backgroundThrottlePackageWhitelist =
                 mBackgroundThrottlePackageWhitelist.getValue();
diff --git a/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java b/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
index d4a8fbd..ed1e654 100644
--- a/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
@@ -155,6 +155,11 @@
      */
     @Override
     public void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
+        int[] runningUserIds = getRunningUserIds();
+        if (runningUserIds.length > 1) {
+            pw.println("running users: u" + Arrays.toString(runningUserIds));
+        }
+
         ActivityManagerInternal activityManagerInternal = getActivityManagerInternal();
         if (activityManagerInternal == null) {
             return;
diff --git a/services/core/java/com/android/server/location/injector/UserInfoHelper.java b/services/core/java/com/android/server/location/injector/UserInfoHelper.java
index 0fcc1ec..c835370 100644
--- a/services/core/java/com/android/server/location/injector/UserInfoHelper.java
+++ b/services/core/java/com/android/server/location/injector/UserInfoHelper.java
@@ -18,6 +18,7 @@
 
 import static com.android.server.location.LocationManagerService.D;
 import static com.android.server.location.LocationManagerService.TAG;
+import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
 import static com.android.server.location.injector.UserInfoHelper.UserListener.CURRENT_USER_CHANGED;
 import static com.android.server.location.injector.UserInfoHelper.UserListener.USER_STARTED;
 import static com.android.server.location.injector.UserInfoHelper.UserListener.USER_STOPPED;
@@ -105,6 +106,7 @@
             Log.d(TAG, "current user changed from u" + Arrays.toString(fromUserIds) + " to u"
                     + Arrays.toString(toUserIds));
         }
+        EVENT_LOG.logUserSwitched(fromUserId, toUserId);
 
         for (UserListener listener : mListeners) {
             for (int userId : fromUserIds) {
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index dc8b1d0..102263b 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -37,6 +37,7 @@
 import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
 import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
 import static com.android.server.location.LocationPermissions.PERMISSION_NONE;
+import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
 
 import static java.lang.Math.max;
 import static java.lang.Math.min;
@@ -94,7 +95,6 @@
 import com.android.server.LocalServices;
 import com.android.server.location.LocationPermissions;
 import com.android.server.location.LocationPermissions.PermissionLevel;
-import com.android.server.location.eventlog.LocationEventLog;
 import com.android.server.location.fudger.LocationFudger;
 import com.android.server.location.injector.AlarmHelper;
 import com.android.server.location.injector.AppForegroundHelper;
@@ -333,7 +333,7 @@
                         + getRequest());
             }
 
-            mEventLog.logProviderClientRegistered(mName, getIdentity(), super.getRequest());
+            EVENT_LOG.logProviderClientRegistered(mName, getIdentity(), super.getRequest());
 
             // initialization order is important as there are ordering dependencies
             mPermitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
@@ -345,7 +345,7 @@
             onProviderListenerRegister();
 
             if (mForeground) {
-                mEventLog.logProviderClientForeground(mName, getIdentity());
+                EVENT_LOG.logProviderClientForeground(mName, getIdentity());
             }
         }
 
@@ -358,7 +358,7 @@
 
             onProviderListenerUnregister();
 
-            mEventLog.logProviderClientUnregistered(mName, getIdentity());
+            EVENT_LOG.logProviderClientUnregistered(mName, getIdentity());
 
             if (D) {
                 Log.d(TAG, mName + " provider removed registration from " + getIdentity());
@@ -383,7 +383,7 @@
                 Preconditions.checkState(Thread.holdsLock(mLock));
             }
 
-            mEventLog.logProviderClientActive(mName, getIdentity());
+            EVENT_LOG.logProviderClientActive(mName, getIdentity());
 
             if (!getRequest().isHiddenFromAppOps()) {
                 mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey());
@@ -406,7 +406,7 @@
 
             onProviderListenerInactive();
 
-            mEventLog.logProviderClientInactive(mName, getIdentity());
+            EVENT_LOG.logProviderClientInactive(mName, getIdentity());
         }
 
         /**
@@ -543,9 +543,9 @@
                 mForeground = foreground;
 
                 if (mForeground) {
-                    mEventLog.logProviderClientForeground(mName, getIdentity());
+                    EVENT_LOG.logProviderClientForeground(mName, getIdentity());
                 } else {
-                    mEventLog.logProviderClientBackground(mName, getIdentity());
+                    EVENT_LOG.logProviderClientBackground(mName, getIdentity());
                 }
 
                 // note that onProviderLocationRequestChanged() is always called
@@ -654,7 +654,7 @@
     protected abstract class LocationRegistration extends Registration implements
             OnAlarmListener, ProviderEnabledListener {
 
-        private final PowerManager.WakeLock mWakeLock;
+        final PowerManager.WakeLock mWakeLock;
 
         private volatile ProviderTransport mProviderTransport;
         private int mNumLocationsDelivered = 0;
@@ -879,7 +879,7 @@
 
                     listener.deliverOnLocationChanged(deliverLocationResult,
                             mUseWakeLock ? mWakeLock::release : null);
-                    mEventLog.logProviderDeliveredLocations(mName, locationResult.size(),
+                    EVENT_LOG.logProviderDeliveredLocations(mName, locationResult.size(),
                             getIdentity());
                 }
 
@@ -1178,7 +1178,7 @@
 
                     // we currently don't hold a wakelock for getCurrentLocation deliveries
                     listener.deliverOnLocationChanged(deliverLocationResult, null);
-                    mEventLog.logProviderDeliveredLocations(mName,
+                    EVENT_LOG.logProviderDeliveredLocations(mName,
                             locationResult != null ? locationResult.size() : 0, getIdentity());
                 }
 
@@ -1247,7 +1247,6 @@
 
     private final CopyOnWriteArrayList<IProviderRequestListener> mProviderRequestListeners;
 
-    protected final LocationEventLog mEventLog;
     protected final LocationManagerInternal mLocationManagerInternal;
     protected final SettingsHelper mSettingsHelper;
     protected final UserInfoHelper mUserHelper;
@@ -1300,7 +1299,7 @@
     @GuardedBy("mLock")
     private @Nullable OnProviderLocationTagsChangeListener mOnLocationTagsChangeListener;
 
-    public LocationProviderManager(Context context, Injector injector, LocationEventLog eventLog,
+    public LocationProviderManager(Context context, Injector injector,
             String name, @Nullable PassiveLocationProviderManager passiveManager) {
         mContext = context;
         mName = Objects.requireNonNull(name);
@@ -1312,7 +1311,6 @@
         mEnabledListeners = new ArrayList<>();
         mProviderRequestListeners = new CopyOnWriteArrayList<>();
 
-        mEventLog = eventLog;
         mLocationManagerInternal = Objects.requireNonNull(
                 LocalServices.getService(LocationManagerInternal.class));
         mSettingsHelper = injector.getSettingsHelper();
@@ -1477,7 +1475,7 @@
         synchronized (mLock) {
             Preconditions.checkState(mState != STATE_STOPPED);
 
-            mEventLog.logProviderMocked(mName, provider != null);
+            EVENT_LOG.logProviderMocked(mName, provider != null);
 
             final long identity = Binder.clearCallingIdentity();
             try {
@@ -1966,8 +1964,8 @@
     }
 
     @GuardedBy("mLock")
-    private void setProviderRequest(ProviderRequest request) {
-        mEventLog.logProviderUpdateRequest(mName, request);
+    void setProviderRequest(ProviderRequest request) {
+        EVENT_LOG.logProviderUpdateRequest(mName, request);
         mProvider.getController().setRequest(request);
 
         FgThread.getHandler().post(() -> {
@@ -2324,7 +2322,7 @@
             }
 
             // don't log location received for passive provider because it's spammy
-            mEventLog.logProviderReceivedLocations(mName, filtered.size());
+            EVENT_LOG.logProviderReceivedLocations(mName, filtered.size());
         } else {
             // passive provider should get already filtered results as input
             filtered = locationResult;
@@ -2424,7 +2422,7 @@
             if (D) {
                 Log.d(TAG, "[u" + userId + "] " + mName + " provider enabled = " + enabled);
             }
-            mEventLog.logProviderEnabled(mName, userId, enabled);
+            EVENT_LOG.logProviderEnabled(mName, userId, enabled);
         }
 
         // clear last locations if we become disabled
@@ -2464,7 +2462,7 @@
         updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
     }
 
-    private @Nullable Location getPermittedLocation(@Nullable Location fineLocation,
+    @Nullable Location getPermittedLocation(@Nullable Location fineLocation,
             @PermissionLevel int permissionLevel) {
         switch (permissionLevel) {
             case PERMISSION_FINE:
@@ -2477,7 +2475,7 @@
         }
     }
 
-    private @Nullable LocationResult getPermittedLocationResult(
+    @Nullable LocationResult getPermittedLocationResult(
             @Nullable LocationResult fineLocationResult, @PermissionLevel int permissionLevel) {
         switch (permissionLevel) {
             case PERMISSION_FINE:
@@ -2538,6 +2536,8 @@
         private @Nullable Location mFineBypassLocation;
         private @Nullable Location mCoarseBypassLocation;
 
+        LastLocation() {}
+
         public void clearMock() {
             if (mFineLocation != null && mFineLocation.isFromMockProvider()) {
                 mFineLocation = null;
diff --git a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
index 027f4e9..b35af4f 100644
--- a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
@@ -24,7 +24,6 @@
 import android.os.Binder;
 
 import com.android.internal.util.Preconditions;
-import com.android.server.location.eventlog.LocationEventLog;
 import com.android.server.location.injector.Injector;
 
 import java.util.Collection;
@@ -34,9 +33,8 @@
  */
 public class PassiveLocationProviderManager extends LocationProviderManager {
 
-    public PassiveLocationProviderManager(Context context, Injector injector,
-            LocationEventLog eventLog) {
-        super(context, injector, eventLog, LocationManager.PASSIVE_PROVIDER, null);
+    public PassiveLocationProviderManager(Context context, Injector injector) {
+        super(context, injector, LocationManager.PASSIVE_PROVIDER, null);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
index 6f4aa64..ab7e526 100644
--- a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
@@ -21,6 +21,7 @@
 import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
 import static com.android.server.location.LocationManagerService.D;
 import static com.android.server.location.LocationManagerService.TAG;
+import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
 
 import android.annotation.Nullable;
 import android.location.Location;
@@ -33,7 +34,6 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.DeviceIdleInternal;
 import com.android.server.FgThread;
-import com.android.server.location.eventlog.LocationEventLog;
 import com.android.server.location.injector.DeviceIdleHelper;
 import com.android.server.location.injector.DeviceStationaryHelper;
 import com.android.server.location.injector.Injector;
@@ -54,12 +54,11 @@
 
     private static final long MAX_STATIONARY_LOCATION_AGE_MS = 30000;
 
-    private final Object mLock = new Object();
+    final Object mLock = new Object();
 
     private final String mName;
     private final DeviceIdleHelper mDeviceIdleHelper;
     private final DeviceStationaryHelper mDeviceStationaryHelper;
-    private final LocationEventLog mEventLog;
 
     @GuardedBy("mLock")
     private boolean mDeviceIdle = false;
@@ -72,21 +71,19 @@
     @GuardedBy("mLock")
     private ProviderRequest mOutgoingRequest = ProviderRequest.EMPTY_REQUEST;
     @GuardedBy("mLock")
-    private long mThrottlingIntervalMs = INTERVAL_DISABLED;
+    long mThrottlingIntervalMs = INTERVAL_DISABLED;
     @GuardedBy("mLock")
-    private @Nullable DeliverLastLocationRunnable mDeliverLastLocationCallback = null;
-
+    @Nullable DeliverLastLocationRunnable mDeliverLastLocationCallback = null;
     @GuardedBy("mLock")
-    private @Nullable Location mLastLocation;
+    @Nullable Location mLastLocation;
 
     public StationaryThrottlingLocationProvider(String name, Injector injector,
-            AbstractLocationProvider delegate, LocationEventLog eventLog) {
+            AbstractLocationProvider delegate) {
         super(DIRECT_EXECUTOR, delegate);
 
         mName = name;
         mDeviceIdleHelper = injector.getDeviceIdleHelper();
         mDeviceStationaryHelper = injector.getDeviceStationaryHelper();
-        mEventLog = eventLog;
 
         // must be last statement in the constructor because reference is escaping
         initializeDelegate();
@@ -209,7 +206,7 @@
                 if (D) {
                     Log.d(TAG, mName + " provider stationary throttled");
                 }
-                mEventLog.logProviderStationaryThrottled(mName, true);
+                EVENT_LOG.logProviderStationaryThrottled(mName, true);
             }
 
             if (mDeliverLastLocationCallback != null) {
@@ -227,7 +224,7 @@
             }
         } else {
             if (oldThrottlingIntervalMs != INTERVAL_DISABLED) {
-                mEventLog.logProviderStationaryThrottled(mName, false);
+                EVENT_LOG.logProviderStationaryThrottled(mName, false);
                 if (D) {
                     Log.d(TAG, mName + " provider stationary unthrottled");
                 }
@@ -257,6 +254,9 @@
     }
 
     private class DeliverLastLocationRunnable implements Runnable {
+
+        DeliverLastLocationRunnable() {}
+
         @Override
         public void run() {
             Location location;
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
index c86e49b..5df7870 100644
--- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
@@ -19,7 +19,6 @@
 import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
 
 import android.annotation.Nullable;
-import android.content.ComponentName;
 import android.content.Context;
 import android.location.Location;
 import android.location.LocationResult;
@@ -28,42 +27,46 @@
 import android.location.provider.ProviderProperties;
 import android.location.provider.ProviderRequest;
 import android.location.util.identity.CallerIdentity;
-import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.text.TextUtils;
 import android.util.ArraySet;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.ArrayUtils;
+import com.android.server.FgThread;
 import com.android.server.location.provider.AbstractLocationProvider;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier.BoundServiceInfo;
 import com.android.server.servicewatcher.ServiceWatcher;
-import com.android.server.servicewatcher.ServiceWatcher.BoundService;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceListener;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Objects;
 
 /**
  * Proxy for ILocationProvider implementations.
  */
-public class ProxyLocationProvider extends AbstractLocationProvider {
+public class ProxyLocationProvider extends AbstractLocationProvider implements
+        ServiceListener<BoundServiceInfo> {
 
-    private static final String KEY_EXTRA_ATTRIBUTION_TAGS = "android:location_allow_listed_tags";
-    private static final String EXTRA_ATTRIBUTION_TAGS_SEPARATOR = ";";
+    private static final String EXTRA_LOCATION_TAGS = "android:location_allow_listed_tags";
+    private static final String LOCATION_TAGS_SEPARATOR = ";";
+
+    private static final long RESET_DELAY_MS = 1000;
 
     /**
      * Creates and registers this proxy. If no suitable service is available for the proxy, returns
      * null.
      */
     @Nullable
-    public static ProxyLocationProvider create(Context context, String action,
+    public static ProxyLocationProvider create(Context context, String provider, String action,
             int enableOverlayResId, int nonOverlayPackageResId) {
-        ProxyLocationProvider proxy = new ProxyLocationProvider(context, action, enableOverlayResId,
-                nonOverlayPackageResId);
+        ProxyLocationProvider proxy = new ProxyLocationProvider(context, provider, action,
+                enableOverlayResId, nonOverlayPackageResId);
         if (proxy.checkServiceResolves()) {
             return proxy;
         } else {
@@ -80,21 +83,24 @@
     final ArrayList<Runnable> mFlushListeners = new ArrayList<>(0);
 
     @GuardedBy("mLock")
-    Proxy mProxy;
+    @Nullable Runnable mResetter;
     @GuardedBy("mLock")
-    @Nullable ComponentName mService;
+    @Nullable Proxy mProxy;
+    @GuardedBy("mLock")
+    @Nullable BoundServiceInfo mBoundServiceInfo;
 
     private volatile ProviderRequest mRequest;
 
-    private ProxyLocationProvider(Context context, String action, int enableOverlayResId,
-            int nonOverlayPackageResId) {
+    private ProxyLocationProvider(Context context, String provider, String action,
+            int enableOverlayResId, int nonOverlayPackageResId) {
         // safe to use direct executor since our locks are not acquired in a code path invoked by
         // our owning provider
         super(DIRECT_EXECUTOR, null, null, Collections.emptySet());
 
         mContext = context;
-        mServiceWatcher = new ServiceWatcher(context, action, this::onBind,
-                this::onUnbind, enableOverlayResId, nonOverlayPackageResId);
+        mServiceWatcher = ServiceWatcher.create(context, provider,
+                new CurrentUserServiceSupplier(context, action, enableOverlayResId,
+                        nonOverlayPackageResId), this);
 
         mProxy = null;
         mRequest = ProviderRequest.EMPTY_REQUEST;
@@ -104,21 +110,13 @@
         return mServiceWatcher.checkServiceResolves();
     }
 
-    private void onBind(IBinder binder, BoundService boundService) throws RemoteException {
+    @Override
+    public void onBind(IBinder binder, BoundServiceInfo boundServiceInfo) throws RemoteException {
         ILocationProvider provider = ILocationProvider.Stub.asInterface(binder);
 
         synchronized (mLock) {
             mProxy = new Proxy();
-            mService = boundService.component;
-
-            // update extra attribution tag info from manifest
-            if (boundService.metadata != null) {
-                String tagsList = boundService.metadata.getString(KEY_EXTRA_ATTRIBUTION_TAGS);
-                if (tagsList != null) {
-                    setExtraAttributionTags(
-                            new ArraySet<>(tagsList.split(EXTRA_ATTRIBUTION_TAGS_SEPARATOR)));
-                }
-            }
+            mBoundServiceInfo = boundServiceInfo;
 
             provider.setLocationProviderManager(mProxy);
 
@@ -129,12 +127,30 @@
         }
     }
 
-    private void onUnbind() {
+    @Override
+    public void onUnbind() {
         Runnable[] flushListeners;
         synchronized (mLock) {
             mProxy = null;
-            mService = null;
-            setState(prevState -> State.EMPTY_STATE);
+            mBoundServiceInfo = null;
+
+            // we need to clear the state - but most disconnections are very temporary. we give a
+            // grace period where we don't clear the state immediately so that transient
+            // interruptions are not necessarily visible to downstream clients
+            if (mResetter == null) {
+                mResetter = new Runnable() {
+                    @Override
+                    public void run() {
+                        synchronized (mLock) {
+                            if (mResetter == this) {
+                                setState(prevState -> State.EMPTY_STATE);
+                            }
+                        }
+                    }
+                };
+                FgThread.getHandler().postDelayed(mResetter, RESET_DELAY_MS);
+            }
+
             flushListeners = mFlushListeners.toArray(new Runnable[0]);
             mFlushListeners.clear();
         }
@@ -166,7 +182,7 @@
 
     @Override
     protected void onFlush(Runnable callback) {
-        mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+        mServiceWatcher.runOnBinder(new ServiceWatcher.BinderOperation() {
             @Override
             public void run(IBinder binder) throws RemoteException {
                 ILocationProvider provider = ILocationProvider.Stub.asInterface(binder);
@@ -206,20 +222,7 @@
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        mServiceWatcher.dump(fd, pw, args);
-    }
-
-    private static String guessPackageName(Context context, int uid, String packageName) {
-        String[] packageNames = context.getPackageManager().getPackagesForUid(uid);
-        if (packageNames == null || packageNames.length == 0) {
-            // illegal state exception will propagate back through binders
-            throw new IllegalStateException(
-                    "location provider from uid " + uid + " has no package information");
-        } else if (ArrayUtils.contains(packageNames, packageName)) {
-            return packageName;
-        } else {
-            return packageNames[0];
-        }
+        mServiceWatcher.dump(pw);
     }
 
     private class Proxy extends ILocationProviderManager.Stub {
@@ -229,26 +232,37 @@
         // executed on binder thread
         @Override
         public void onInitialize(boolean allowed, ProviderProperties properties,
-                @Nullable String packageName, @Nullable String attributionTag) {
+                @Nullable String attributionTag) {
             synchronized (mLock) {
                 if (mProxy != this) {
                     return;
                 }
 
-                CallerIdentity identity;
-                if (packageName == null) {
-                    packageName = guessPackageName(mContext, Binder.getCallingUid(),
-                            Objects.requireNonNull(mService).getPackageName());
-                    // unsafe is ok since the package is coming direct from the package manager here
-                    identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag);
-                } else {
-                    identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag);
+                if (mResetter != null) {
+                    FgThread.getHandler().removeCallbacks(mResetter);
+                    mResetter = null;
                 }
 
-                setState(prevState -> prevState
+                // set extra attribution tags from manifest if necessary
+                String[] attributionTags = new String[0];
+                if (mBoundServiceInfo.getMetadata() != null) {
+                    String tagsStr = mBoundServiceInfo.getMetadata().getString(EXTRA_LOCATION_TAGS);
+                    if (!TextUtils.isEmpty(tagsStr)) {
+                        attributionTags = tagsStr.split(LOCATION_TAGS_SEPARATOR);
+                    }
+                }
+                ArraySet<String> extraAttributionTags = new ArraySet<>(attributionTags);
+
+                // unsafe is ok since we trust the package name already
+                CallerIdentity identity = CallerIdentity.fromBinderUnsafe(
+                        mBoundServiceInfo.getComponentName().getPackageName(),
+                        attributionTag);
+
+                setState(prevState -> State.EMPTY_STATE
                         .withAllowed(allowed)
                         .withProperties(properties)
-                        .withIdentity(identity));
+                        .withIdentity(identity)
+                        .withExtraAttributionTags(extraAttributionTags));
             }
         }
 
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 46ece74..db2e908 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -563,8 +563,8 @@
                 final PowerExemptionManager powerExemptionManager = userContext.getSystemService(
                         PowerExemptionManager.class);
                 powerExemptionManager.addToTemporaryAllowList(targetPackage,
-                        FGS_STARTS_TEMP_ALLOWLIST_DURATION_MS,
-                        PowerExemptionManager.REASON_MEDIA_SESSION_CALLBACK, reason);
+                        PowerExemptionManager.REASON_MEDIA_SESSION_CALLBACK, reason,
+                        FGS_STARTS_TEMP_ALLOWLIST_DURATION_MS);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
diff --git a/services/core/java/com/android/server/net/IpConfigStore.java b/services/core/java/com/android/server/net/IpConfigStore.java
index cc3a002..df1eb6d 100644
--- a/services/core/java/com/android/server/net/IpConfigStore.java
+++ b/services/core/java/com/android/server/net/IpConfigStore.java
@@ -22,7 +22,6 @@
 import android.net.IpConfiguration.ProxySettings;
 import android.net.LinkAddress;
 import android.net.ProxyInfo;
-import android.net.RouteInfo;
 import android.net.StaticIpConfiguration;
 import android.net.Uri;
 import android.util.ArrayMap;
@@ -42,6 +41,8 @@
 import java.io.InputStream;
 import java.net.Inet4Address;
 import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
 
 public class IpConfigStore {
     private static final String TAG = "IpConfigStore";
@@ -83,25 +84,25 @@
         boolean written = false;
 
         try {
-            switch (config.ipAssignment) {
+            switch (config.getIpAssignment()) {
                 case STATIC:
                     out.writeUTF(IP_ASSIGNMENT_KEY);
-                    out.writeUTF(config.ipAssignment.toString());
-                    StaticIpConfiguration staticIpConfiguration = config.staticIpConfiguration;
+                    out.writeUTF(config.getIpAssignment().toString());
+                    StaticIpConfiguration staticIpConfiguration = config.getStaticIpConfiguration();
                     if (staticIpConfiguration != null) {
-                        if (staticIpConfiguration.ipAddress != null) {
-                            LinkAddress ipAddress = staticIpConfiguration.ipAddress;
+                        if (staticIpConfiguration.getIpAddress() != null) {
+                            LinkAddress ipAddress = staticIpConfiguration.getIpAddress();
                             out.writeUTF(LINK_ADDRESS_KEY);
                             out.writeUTF(ipAddress.getAddress().getHostAddress());
                             out.writeInt(ipAddress.getPrefixLength());
                         }
-                        if (staticIpConfiguration.gateway != null) {
+                        if (staticIpConfiguration.getGateway() != null) {
                             out.writeUTF(GATEWAY_KEY);
                             out.writeInt(0);  // Default route.
                             out.writeInt(1);  // Have a gateway.
-                            out.writeUTF(staticIpConfiguration.gateway.getHostAddress());
+                            out.writeUTF(staticIpConfiguration.getGateway().getHostAddress());
                         }
-                        for (InetAddress inetAddr : staticIpConfiguration.dnsServers) {
+                        for (InetAddress inetAddr : staticIpConfiguration.getDnsServers()) {
                             out.writeUTF(DNS_KEY);
                             out.writeUTF(inetAddr.getHostAddress());
                         }
@@ -110,7 +111,7 @@
                     break;
                 case DHCP:
                     out.writeUTF(IP_ASSIGNMENT_KEY);
-                    out.writeUTF(config.ipAssignment.toString());
+                    out.writeUTF(config.getIpAssignment().toString());
                     written = true;
                     break;
                 case UNASSIGNED:
@@ -121,13 +122,13 @@
                     break;
             }
 
-            switch (config.proxySettings) {
+            switch (config.getProxySettings()) {
                 case STATIC:
-                    ProxyInfo proxyProperties = config.httpProxy;
+                    ProxyInfo proxyProperties = config.getHttpProxy();
                     String exclusionList = ProxyUtils.exclusionListAsString(
                             proxyProperties.getExclusionList());
                     out.writeUTF(PROXY_SETTINGS_KEY);
-                    out.writeUTF(config.proxySettings.toString());
+                    out.writeUTF(config.getProxySettings().toString());
                     out.writeUTF(PROXY_HOST_KEY);
                     out.writeUTF(proxyProperties.getHost());
                     out.writeUTF(PROXY_PORT_KEY);
@@ -139,16 +140,16 @@
                     written = true;
                     break;
                 case PAC:
-                    ProxyInfo proxyPacProperties = config.httpProxy;
+                    ProxyInfo proxyPacProperties = config.getHttpProxy();
                     out.writeUTF(PROXY_SETTINGS_KEY);
-                    out.writeUTF(config.proxySettings.toString());
+                    out.writeUTF(config.getProxySettings().toString());
                     out.writeUTF(PROXY_PAC_FILE);
                     out.writeUTF(proxyPacProperties.getPacFileUrl().toString());
                     written = true;
                     break;
                 case NONE:
                     out.writeUTF(PROXY_SETTINGS_KEY);
-                    out.writeUTF(config.proxySettings.toString());
+                    out.writeUTF(config.getProxySettings().toString());
                     written = true;
                     break;
                 case UNASSIGNED:
@@ -267,11 +268,14 @@
                 IpAssignment ipAssignment = IpAssignment.DHCP;
                 ProxySettings proxySettings = ProxySettings.NONE;
                 StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration();
+                LinkAddress linkAddress = null;
+                InetAddress gatewayAddress = null;
                 String proxyHost = null;
                 String pacFileUrl = null;
                 int proxyPort = -1;
                 String exclusionList = null;
                 String key;
+                final List<InetAddress> dnsServers = new ArrayList<>();
 
                 do {
                     key = in.readUTF();
@@ -286,15 +290,15 @@
                         } else if (key.equals(IP_ASSIGNMENT_KEY)) {
                             ipAssignment = IpAssignment.valueOf(in.readUTF());
                         } else if (key.equals(LINK_ADDRESS_KEY)) {
-                            LinkAddress linkAddr =
+                            LinkAddress parsedLinkAddress =
                                     new LinkAddress(
                                             InetAddresses.parseNumericAddress(in.readUTF()),
                                             in.readInt());
-                            if (linkAddr.getAddress() instanceof Inet4Address &&
-                                    staticIpConfiguration.ipAddress == null) {
-                                staticIpConfiguration.ipAddress = linkAddr;
+                            if (parsedLinkAddress.getAddress() instanceof Inet4Address
+                                    && linkAddress == null) {
+                                linkAddress = parsedLinkAddress;
                             } else {
-                                loge("Non-IPv4 or duplicate address: " + linkAddr);
+                                loge("Non-IPv4 or duplicate address: " + parsedLinkAddress);
                             }
                         } else if (key.equals(GATEWAY_KEY)) {
                             LinkAddress dest = null;
@@ -302,8 +306,8 @@
                             if (version == 1) {
                                 // only supported default gateways - leave the dest/prefix empty
                                 gateway = InetAddresses.parseNumericAddress(in.readUTF());
-                                if (staticIpConfiguration.gateway == null) {
-                                    staticIpConfiguration.gateway = gateway;
+                                if (gatewayAddress == null) {
+                                    gatewayAddress = gateway;
                                 } else {
                                     loge("Duplicate gateway: " + gateway.getHostAddress());
                                 }
@@ -317,17 +321,18 @@
                                 if (in.readInt() == 1) {
                                     gateway = InetAddresses.parseNumericAddress(in.readUTF());
                                 }
-                                RouteInfo route = new RouteInfo(dest, gateway);
-                                if (route.isIPv4Default() &&
-                                        staticIpConfiguration.gateway == null) {
-                                    staticIpConfiguration.gateway = gateway;
+                                // If the destination is a default IPv4 route, use the gateway
+                                // address unless already set.
+                                if (dest.getAddress() instanceof Inet4Address
+                                        && dest.getPrefixLength() == 0 && gatewayAddress == null) {
+                                    gatewayAddress = gateway;
                                 } else {
-                                    loge("Non-IPv4 default or duplicate route: " + route);
+                                    loge("Non-IPv4 default or duplicate route: "
+                                            + dest.getAddress());
                                 }
                             }
                         } else if (key.equals(DNS_KEY)) {
-                            staticIpConfiguration.dnsServers.add(
-                                    InetAddresses.parseNumericAddress(in.readUTF()));
+                            dnsServers.add(InetAddresses.parseNumericAddress(in.readUTF()));
                         } else if (key.equals(PROXY_SETTINGS_KEY)) {
                             proxySettings = ProxySettings.valueOf(in.readUTF());
                         } else if (key.equals(PROXY_HOST_KEY)) {
@@ -348,25 +353,31 @@
                     }
                 } while (true);
 
+                staticIpConfiguration = new StaticIpConfiguration.Builder()
+                    .setIpAddress(linkAddress)
+                    .setGateway(gatewayAddress)
+                    .setDnsServers(dnsServers)
+                    .build();
+
                 if (uniqueToken != null) {
                     IpConfiguration config = new IpConfiguration();
                     networks.put(uniqueToken, config);
 
                     switch (ipAssignment) {
                         case STATIC:
-                            config.staticIpConfiguration = staticIpConfiguration;
-                            config.ipAssignment = ipAssignment;
+                            config.setStaticIpConfiguration(staticIpConfiguration);
+                            config.setIpAssignment(ipAssignment);
                             break;
                         case DHCP:
-                            config.ipAssignment = ipAssignment;
+                            config.setIpAssignment(ipAssignment);
                             break;
                         case UNASSIGNED:
                             loge("BUG: Found UNASSIGNED IP on file, use DHCP");
-                            config.ipAssignment = IpAssignment.DHCP;
+                            config.setIpAssignment(IpAssignment.DHCP);
                             break;
                         default:
                             loge("Ignore invalid ip assignment while reading.");
-                            config.ipAssignment = IpAssignment.UNASSIGNED;
+                            config.setIpAssignment(IpAssignment.UNASSIGNED);
                             break;
                     }
 
@@ -374,25 +385,25 @@
                         case STATIC:
                             ProxyInfo proxyInfo = ProxyInfo.buildDirectProxy(proxyHost, proxyPort,
                                     ProxyUtils.exclusionStringAsList(exclusionList));
-                            config.proxySettings = proxySettings;
-                            config.httpProxy = proxyInfo;
+                            config.setProxySettings(proxySettings);
+                            config.setHttpProxy(proxyInfo);
                             break;
                         case PAC:
                             ProxyInfo proxyPacProperties =
                                     ProxyInfo.buildPacProxy(Uri.parse(pacFileUrl));
-                            config.proxySettings = proxySettings;
-                            config.httpProxy = proxyPacProperties;
+                            config.setProxySettings(proxySettings);
+                            config.setHttpProxy(proxyPacProperties);
                             break;
                         case NONE:
-                            config.proxySettings = proxySettings;
+                            config.setProxySettings(proxySettings);
                             break;
                         case UNASSIGNED:
                             loge("BUG: Found UNASSIGNED proxy on file, use NONE");
-                            config.proxySettings = ProxySettings.NONE;
+                            config.setProxySettings(ProxySettings.NONE);
                             break;
                         default:
                             loge("Ignore invalid proxy settings while reading");
-                            config.proxySettings = ProxySettings.UNASSIGNED;
+                            config.setProxySettings(ProxySettings.UNASSIGNED);
                             break;
                     }
                 } else {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index d649925..16eac91 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -58,7 +58,9 @@
 import static android.net.NetworkPolicy.LIMIT_DISABLED;
 import static android.net.NetworkPolicy.SNOOZE_NEVER;
 import static android.net.NetworkPolicy.WARNING_DISABLED;
+import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_FOREGROUND;
 import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_MASK;
+import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_SYSTEM;
 import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_USER_EXEMPTED;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_FOREGROUND;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
@@ -4624,8 +4626,8 @@
         newBlockedReasons |= (mRestrictBackground ? BLOCKED_METERED_REASON_DATA_SAVER : 0);
         newBlockedReasons |= (isDenied ? BLOCKED_METERED_REASON_USER_RESTRICTED : 0);
 
-        newAllowedReasons |= (isSystem(uid) ? ALLOWED_REASON_SYSTEM : 0);
-        newAllowedReasons |= (isForeground ? ALLOWED_REASON_FOREGROUND : 0);
+        newAllowedReasons |= (isSystem(uid) ? ALLOWED_METERED_REASON_SYSTEM : 0);
+        newAllowedReasons |= (isForeground ? ALLOWED_METERED_REASON_FOREGROUND : 0);
         newAllowedReasons |= (isAllowed ? ALLOWED_METERED_REASON_USER_EXEMPTED : 0);
 
         if (LOGV) {
@@ -4699,18 +4701,18 @@
 
             // Dispatch changed rule to existing listeners.
             mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRules).sendToTarget();
+        }
 
-            final int oldEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
-            uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
-                    & ~BLOCKED_METERED_REASON_MASK) | newBlockedReasons;
-            uidBlockedState.allowedReasons = (uidBlockedState.allowedReasons
-                    & ~ALLOWED_METERED_REASON_MASK) | newAllowedReasons;
-            uidBlockedState.updateEffectiveBlockedReasons();
-            if (oldEffectiveBlockedReasons != uidBlockedState.effectiveBlockedReasons) {
-                mHandler.obtainMessage(MSG_BLOCKED_REASON_CHANGED, uid,
-                        uidBlockedState.effectiveBlockedReasons, oldEffectiveBlockedReasons)
-                        .sendToTarget();
-            }
+        final int oldEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
+        uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
+                & ~BLOCKED_METERED_REASON_MASK) | newBlockedReasons;
+        uidBlockedState.allowedReasons = (uidBlockedState.allowedReasons
+                & ~ALLOWED_METERED_REASON_MASK) | newAllowedReasons;
+        uidBlockedState.updateEffectiveBlockedReasons();
+        if (oldEffectiveBlockedReasons != uidBlockedState.effectiveBlockedReasons) {
+            mHandler.obtainMessage(MSG_BLOCKED_REASON_CHANGED, uid,
+                    uidBlockedState.effectiveBlockedReasons, oldEffectiveBlockedReasons)
+                    .sendToTarget();
         }
     }
 
@@ -5858,12 +5860,17 @@
                 return;
             }
             if ((allowedReasons & ALLOWED_REASON_SYSTEM) != 0) {
-                effectiveBlockedReasons = BLOCKED_REASON_NONE;
+                effectiveBlockedReasons = (blockedReasons & ALLOWED_METERED_REASON_MASK);
+            }
+            if ((allowedReasons & ALLOWED_METERED_REASON_SYSTEM) != 0) {
+                effectiveBlockedReasons = (blockedReasons & ~ALLOWED_METERED_REASON_MASK);
             }
             if ((allowedReasons & ALLOWED_REASON_FOREGROUND) != 0) {
                 effectiveBlockedReasons &= ~BLOCKED_REASON_BATTERY_SAVER;
                 effectiveBlockedReasons &= ~BLOCKED_REASON_DOZE;
                 effectiveBlockedReasons &= ~BLOCKED_REASON_APP_STANDBY;
+            }
+            if ((allowedReasons & ALLOWED_METERED_REASON_FOREGROUND) != 0) {
                 effectiveBlockedReasons &= ~BLOCKED_METERED_REASON_DATA_SAVER;
                 effectiveBlockedReasons &= ~BLOCKED_METERED_REASON_USER_RESTRICTED;
             }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 68fc14c..08dbd77 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -7490,9 +7490,11 @@
             boolean rateLimitingEnabled =
                     !mToastRateLimitingDisabledUids.contains(record.uid);
             boolean isWithinQuota =
-                    mToastRateLimiter.isWithinQuota(userId, record.pkg, TOAST_QUOTA_TAG);
+                    mToastRateLimiter.isWithinQuota(userId, record.pkg, TOAST_QUOTA_TAG)
+                            || isExemptFromRateLimiting(record.pkg, userId);
 
-            if (tryShowToast(record, rateLimitingEnabled, isWithinQuota)) {
+            if (tryShowToast(
+                    record, rateLimitingEnabled, isWithinQuota)) {
                 scheduleDurationReachedLocked(record, lastToastWasTextRecord);
                 mIsCurrentToastShown = true;
                 if (rateLimitingEnabled) {
@@ -7526,6 +7528,18 @@
         return record.show();
     }
 
+    private boolean isExemptFromRateLimiting(String pkg, int userId) {
+        boolean isExemptFromRateLimiting = false;
+        try {
+            isExemptFromRateLimiting = mPackageManager.checkPermission(
+                    android.Manifest.permission.UNLIMITED_TOASTS, pkg, userId)
+                    == PackageManager.PERMISSION_GRANTED;
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to connect with package manager");
+        }
+        return isExemptFromRateLimiting;
+    }
+
     /** Reports rate limiting toasts compat change (used when the toast was blocked). */
     private void reportCompatRateLimitingToastsChange(int uid) {
         final long id = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index 9c4c510..cc6a824 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -411,8 +411,13 @@
                             processName = stream.readString(Tombstone.PROCESS_NAME);
                             break;
 
-                        case (int) Tombstone.CAUSE:
-                            long token = stream.start(Tombstone.CAUSE);
+                        case (int) Tombstone.CAUSES:
+                            if (!crashReason.equals("")) {
+                                // Causes appear in decreasing order of likelihood. For now we only
+                                // want the most likely crash reason here, so ignore all others.
+                                break;
+                            }
+                            long token = stream.start(Tombstone.CAUSES);
                         cause:
                             while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                                 switch (stream.getFieldNumber()) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 43eeb2a5..b27c0bd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -22903,9 +22903,14 @@
 
     @Override
     public String getWellbeingPackageName() {
-        return CollectionUtils.firstOrNull(
-                mContext.getSystemService(RoleManager.class).getRoleHolders(
-                        RoleManager.ROLE_SYSTEM_WELLBEING));
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return CollectionUtils.firstOrNull(
+                    mContext.getSystemService(RoleManager.class).getRoleHolders(
+                            RoleManager.ROLE_SYSTEM_WELLBEING));
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 302e657..8c3c423 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -20,8 +20,17 @@
 import android.annotation.UserIdInt;
 import android.app.Person;
 import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchResult;
 import android.app.appsearch.AppSearchSession;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.GetByUriRequest;
 import android.app.appsearch.PackageIdentifier;
+import android.app.appsearch.PutDocumentsRequest;
+import android.app.appsearch.RemoveByUriRequest;
+import android.app.appsearch.ReportUsageRequest;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchResults;
+import android.app.appsearch.SearchSpec;
 import android.app.appsearch.SetSchemaRequest;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -36,6 +45,7 @@
 import android.graphics.drawable.Icon;
 import android.os.Binder;
 import android.os.PersistableBundle;
+import android.os.StrictMode;
 import android.text.format.Formatter;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -48,6 +58,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.ConcurrentUtils;
@@ -70,13 +81,15 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CompletableFuture;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -145,9 +158,9 @@
     private static final String KEY_BITMAP_BYTES = "bitmapBytes";
 
     /**
-     * All the shortcuts from the package, keyed on IDs.
+     * An temp in-memory copy of shortcuts for this package that was loaded from xml, keyed on IDs.
      */
-    final private ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
+    final ArraySet<ShortcutInfo> mShortcuts = new ArraySet<>();
 
     /**
      * All the share targets from the package
@@ -207,7 +220,9 @@
     }
 
     public int getShortcutCount() {
-        return mShortcuts.size();
+        final int[] count = new int[1];
+        forEachShortcut(si -> count[0]++);
+        return count[0];
     }
 
     @Override
@@ -221,17 +236,20 @@
         // - Unshadow all shortcuts.
         // - Set disabled reason.
         // - Disable if needed.
-        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
-            ShortcutInfo si = mShortcuts.valueAt(i);
-            mutateShortcut(si.getId(), si, shortcut -> {
-                shortcut.clearFlags(ShortcutInfo.FLAG_SHADOW);
+        forEachShortcutMutateIf(si -> {
+            if (!si.hasFlags(ShortcutInfo.FLAG_SHADOW)
+                    && si.getDisabledReason() == restoreBlockReason
+                    && restoreBlockReason == ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+                return false;
+            }
+            si.clearFlags(ShortcutInfo.FLAG_SHADOW);
 
-                shortcut.setDisabledReason(restoreBlockReason);
-                if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
-                    shortcut.addFlags(ShortcutInfo.FLAG_DISABLED);
-                }
-            });
-        }
+            si.setDisabledReason(restoreBlockReason);
+            if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+                si.addFlags(ShortcutInfo.FLAG_DISABLED);
+            }
+            return true;
+        });
         // Because some launchers may not have been restored (e.g. allowBackup=false),
         // we need to re-calculate the pinned shortcuts.
         refreshPinnedFlags();
@@ -242,7 +260,7 @@
      */
     @Nullable
     public ShortcutInfo findShortcutById(String id) {
-        return mShortcuts.get(id);
+        return getShortcutById(id);
     }
 
     public boolean isShortcutExistsAndInvisibleToPublisher(String id) {
@@ -265,7 +283,7 @@
     }
 
     public void ensureNotImmutable(@NonNull String id, boolean ignoreInvisible) {
-        ensureNotImmutable(mShortcuts.get(id), ignoreInvisible);
+        ensureNotImmutable(findShortcutById(id), ignoreInvisible);
     }
 
     public void ensureImmutableShortcutsNotIncludedWithIds(@NonNull List<String> shortcutIds,
@@ -308,8 +326,9 @@
      * Delete a shortcut by ID. This will *always* remove it even if it's immutable or invisible.
      */
     private ShortcutInfo forceDeleteShortcutInner(@NonNull String id) {
-        final ShortcutInfo shortcut = mShortcuts.remove(id);
+        final ShortcutInfo shortcut = getShortcutById(id);
         if (shortcut != null) {
+            removeShortcut(id);
             mShortcutUser.mService.removeIconLocked(shortcut);
             shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED
                     | ShortcutInfo.FLAG_MANIFEST | ShortcutInfo.FLAG_CACHED_ALL);
@@ -326,10 +345,10 @@
 
         forceDeleteShortcutInner(newShortcut.getId());
 
-        // Extract Icon and update the icon res ID and the bitmap path.
         s.saveIconAndFixUpShortcutLocked(newShortcut);
         s.fixUpShortcutResourceNamesAndValues(newShortcut);
-        mShortcuts.put(newShortcut.getId(), newShortcut);
+
+        saveShortcut(newShortcut);
     }
 
     /**
@@ -347,7 +366,7 @@
 
         newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
 
-        final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
+        final ShortcutInfo oldShortcut = findShortcutById(newShortcut.getId());
         if (oldShortcut != null) {
             // It's an update case.
             // Make sure the target is updatable. (i.e. should be mutable.)
@@ -379,7 +398,7 @@
         newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
 
         changedShortcuts.clear();
-        final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
+        final ShortcutInfo oldShortcut = findShortcutById(newShortcut.getId());
         boolean deleted = false;
 
         if (oldShortcut == null) {
@@ -418,6 +437,16 @@
         }
 
         forceReplaceShortcutInner(newShortcut);
+        // TODO: Report usage can be filed async
+        runInAppSearch(session -> {
+            final AndroidFuture<Boolean> future = new AndroidFuture<>();
+            session.reportUsage(
+                    new ReportUsageRequest.Builder(getPackageName())
+                            .setUri(newShortcut.getId()).build(),
+                    mShortcutUser.mExecutor, result -> future.complete(result.isSuccess()));
+            return future;
+        });
+
         return deleted;
     }
 
@@ -427,19 +456,12 @@
      * @return List of removed shortcuts.
      */
     private List<ShortcutInfo> removeOrphans() {
-        List<ShortcutInfo> removeList = null;
-
-        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
-            final ShortcutInfo si = mShortcuts.valueAt(i);
-
-            if (si.isAlive()) continue;
-
-            if (removeList == null) {
-                removeList = new ArrayList<>();
-            }
+        final List<ShortcutInfo> removeList = new ArrayList<>(1);
+        forEachShortcut(si -> {
+            if (si.isAlive()) return;
             removeList.add(si);
-        }
-        if (removeList != null) {
+        });
+        if (!removeList.isEmpty()) {
             for (int i = removeList.size() - 1; i >= 0; i--) {
                 forceDeleteShortcutInner(removeList.get(i).getId());
             }
@@ -456,20 +478,19 @@
     public List<ShortcutInfo> deleteAllDynamicShortcuts(boolean ignoreInvisible) {
         final long now = mShortcutUser.mService.injectCurrentTimeMillis();
 
-        boolean changed = false;
-        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
-            final ShortcutInfo si = mShortcuts.valueAt(i);
+        final boolean[] changed = new boolean[1];
+        forEachShortcutMutateIf(si -> {
             if (si.isDynamic() && (!ignoreInvisible || si.isVisibleToPublisher())) {
-                changed = true;
+                changed[0] = true;
 
-                mutateShortcut(si.getId(), si, shortcut -> {
-                    shortcut.setTimestamp(now);
-                    shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
-                    shortcut.setRank(0); // It may still be pinned, so clear the rank.
-                });
+                si.setTimestamp(now);
+                si.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
+                si.setRank(0); // It may still be pinned, so clear the rank.
+                return true;
             }
-        }
-        if (changed) {
+            return false;
+        });
+        if (changed[0]) {
             return removeOrphans();
         }
         return null;
@@ -508,7 +529,7 @@
      * @return The deleted shortcut, or null if it was not actually removed because it's pinned.
      */
     public ShortcutInfo deleteLongLivedWithId(@NonNull String shortcutId, boolean ignoreInvisible) {
-        final ShortcutInfo shortcut = mShortcuts.get(shortcutId);
+        final ShortcutInfo shortcut = findShortcutById(shortcutId);
         if (shortcut != null) {
             mutateShortcut(shortcutId, null, si -> si.clearFlags(ShortcutInfo.FLAG_CACHED_ALL));
         }
@@ -551,7 +572,7 @@
         Preconditions.checkState(
                 (disable == (disabledReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED)),
                 "disable and disabledReason disagree: " + disable + " vs " + disabledReason);
-        final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId);
+        final ShortcutInfo oldShortcut = findShortcutById(shortcutId);
 
         if (oldShortcut == null || !oldShortcut.isEnabled()
                 && (ignoreInvisible && !oldShortcut.isVisibleToPublisher())) {
@@ -567,7 +588,7 @@
                 si.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST);
                 if (disable) {
                     si.addFlags(ShortcutInfo.FLAG_DISABLED);
-                    // Do not overwrite the disabled reason if one is alreay set.
+                    // Do not overwrite the disabled reason if one is already set.
                     if (si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
                         si.setDisabledReason(disabledReason);
                     }
@@ -579,7 +600,6 @@
                     si.setActivity(null);
                 }
             });
-
             return null;
         } else {
             forceDeleteShortcutInner(shortcutId);
@@ -596,7 +616,7 @@
     }
 
     public void updateInvisibleShortcutForPinRequestWith(@NonNull ShortcutInfo shortcut) {
-        final ShortcutInfo source = mShortcuts.get(shortcut.getId());
+        final ShortcutInfo source = findShortcutById(shortcut.getId());
         Objects.requireNonNull(source);
 
         mShortcutUser.mService.validateShortcutForPinRequest(shortcut);
@@ -615,13 +635,6 @@
      * <p>Then remove all shortcuts that are not dynamic and no longer pinned either.
      */
     public void refreshPinnedFlags() {
-        // TODO: rewrite this function with proper query (i.e. fetch only pinned shortcuts and
-        //  unpin if it's no longer pinned by any launcher and vice versa)
-        final List<ShortcutInfo> shortcuts = new ArrayList<>(mShortcuts.values());
-        final Map<String, ShortcutInfo> shortcutMap = new ArrayMap<>(shortcuts.size());
-        for (ShortcutInfo si : shortcuts) {
-            shortcutMap.put(si.getId(), si);
-        }
         final Set<String> pinnedShortcuts = new ArraySet<>();
 
         // First, for the pinned set for each launcher, keep track of their id one by one.
@@ -631,31 +644,20 @@
             if (pinned == null || pinned.size() == 0) {
                 return;
             }
-            for (int i = pinned.size() - 1; i >= 0; i--) {
-                final String id = pinned.valueAt(i);
-                final ShortcutInfo si = shortcutMap.get(id);
-                if (si == null) {
-                    // This happens if a launcher pinned shortcuts from this package, then backup&
-                    // restored, but this package doesn't allow backing up.
-                    // In that case the launcher ends up having a dangling pinned shortcuts.
-                    // That's fine, when the launcher is restored, we'll fix it.
-                    continue;
-                }
-                pinnedShortcuts.add(si.getId());
-            }
+            pinnedShortcuts.addAll(pinned);
         });
         // Then, update the pinned state if necessary
-        for (int i = shortcuts.size() - 1; i >= 0; i--) {
-            final ShortcutInfo si = shortcuts.get(i);
+        forEachShortcutMutateIf(si -> {
             if (pinnedShortcuts.contains(si.getId()) && !si.isPinned()) {
-                mutateShortcut(si.getId(), si,
-                        shortcut -> shortcut.addFlags(ShortcutInfo.FLAG_PINNED));
+                si.addFlags(ShortcutInfo.FLAG_PINNED);
+                return true;
             }
             if (!pinnedShortcuts.contains(si.getId()) && si.isPinned()) {
-                mutateShortcut(si.getId(), si, shortcut ->
-                        shortcut.clearFlags(ShortcutInfo.FLAG_PINNED));
+                si.clearFlags(ShortcutInfo.FLAG_PINNED);
+                return true;
             }
-        }
+            return false;
+        });
 
         // Lastly, remove the ones that are no longer pinned, cached nor dynamic.
         removeOrphans();
@@ -764,17 +766,13 @@
             // Restored and the app not installed yet, so don't return any.
             return;
         }
-
         final ShortcutService s = mShortcutUser.mService;
 
         // Set of pinned shortcuts by the calling launcher.
         final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null
                 : s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId)
-                    .getPinnedShortcutIds(getPackageName(), getPackageUserId());
-
-        for (int i = 0; i < mShortcuts.size(); i++) {
-            final ShortcutInfo si = mShortcuts.valueAt(i);
-
+                        .getPinnedShortcutIds(getPackageName(), getPackageUserId());
+        forEachShortcut(si -> {
             // Need to adjust PINNED flag depending on the caller.
             // Basically if the caller is a launcher (callingLauncher != null) and the launcher
             // isn't pinning it, then we need to clear PINNED for this caller.
@@ -784,7 +782,7 @@
             if (!getPinnedByAnyLauncher) {
                 if (si.isFloating() && !si.isCached()) {
                     if (!isPinnedByCaller) {
-                        continue;
+                        return;
                     }
                 }
             }
@@ -804,7 +802,7 @@
                 }
                 result.add(clone);
             }
-        }
+        });
     }
 
     public void resetThrottling() {
@@ -874,7 +872,7 @@
      * the app's Xml resource.
      */
     int getSharingShortcutCount() {
-        if (mShortcuts.isEmpty() || mShareTargets.isEmpty()) {
+        if (getShortcutCount() == 0 || mShareTargets.isEmpty()) {
             return 0;
         }
 
@@ -912,14 +910,12 @@
      * Return the filenames (excluding path names) of icon bitmap files from this package.
      */
     public ArraySet<String> getUsedBitmapFiles() {
-        final ArraySet<String> usedFiles = new ArraySet<>(mShortcuts.size());
-
-        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
-            final ShortcutInfo si = mShortcuts.valueAt(i);
+        final ArraySet<String> usedFiles = new ArraySet<>(1);
+        forEachShortcut(si -> {
             if (si.getBitmapPath() != null) {
                 usedFiles.add(getFileName(si.getBitmapPath()));
             }
-        }
+        });
         return usedFiles;
     }
 
@@ -936,30 +932,29 @@
      * @return false if any of the target activities are no longer enabled.
      */
     private boolean areAllActivitiesStillEnabled() {
-        if (mShortcuts.size() == 0) {
-            return true;
-        }
         final ShortcutService s = mShortcutUser.mService;
 
         // Normally the number of target activities is 1 or so, so no need to use a complex
         // structure like a set.
         final ArrayList<ComponentName> checked = new ArrayList<>(4);
+        final boolean[] reject = new boolean[1];
 
-        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
-            final ShortcutInfo si = mShortcuts.valueAt(i);
+        forEachShortcutStopWhen(si -> {
             final ComponentName activity = si.getActivity();
 
             if (checked.contains(activity)) {
-                continue; // Already checked.
+                return false; // Already checked.
             }
             checked.add(activity);
 
             if ((activity != null)
                     && !s.injectIsActivityEnabledAndExported(activity, getOwnerUserId())) {
-                return false;
+                reject[0] = true;
+                return true; // Found at least 1 activity is disabled, so skip the rest.
             }
-        }
-        return true;
+            return false;
+        });
+        return !reject[0];
     }
 
     /**
@@ -1042,33 +1037,32 @@
 
         // See if there are any shortcuts that were prevented restoring because the app was of a
         // lower version, and re-enable them.
-        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
-            final ShortcutInfo si = mShortcuts.valueAt(i);
+        forEachShortcutMutateIf(si -> {
             if (si.getDisabledReason() != ShortcutInfo.DISABLED_REASON_VERSION_LOWER) {
-                continue;
+                return false;
             }
             if (getPackageInfo().getBackupSourceVersionCode() > newVersionCode) {
                 if (ShortcutService.DEBUG) {
                     Slog.d(TAG, String.format("Shortcut %s require version %s, still not restored.",
                             si.getId(), getPackageInfo().getBackupSourceVersionCode()));
                 }
-                continue;
+                return false;
             }
             Slog.i(TAG, String.format("Restoring shortcut: %s", si.getId()));
-            mutateShortcut(si.getId(), si, shortcut -> {
-                shortcut.clearFlags(ShortcutInfo.FLAG_DISABLED);
-                shortcut.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
-            });
-        }
+            if (si.hasFlags(ShortcutInfo.FLAG_DISABLED)
+                    || si.getDisabledReason() != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+                si.clearFlags(ShortcutInfo.FLAG_DISABLED);
+                si.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
+                return true;
+            }
+            return false;
+        });
 
         // For existing shortcuts, update timestamps if they have any resources.
         // Also check if shortcuts' activities are still main activities.  Otherwise, disable them.
         if (!isNewApp) {
-            Resources publisherRes = null;
-
-            for (int i = mShortcuts.size() - 1; i >= 0; i--) {
-                final ShortcutInfo si = mShortcuts.valueAt(i);
-
+            final Resources publisherRes = getPackageResources();
+            forEachShortcutMutateIf(si -> {
                 // Disable dynamic shortcuts whose target activity is gone.
                 if (si.isDynamic()) {
                     if (si.getActivity() == null) {
@@ -1081,33 +1075,26 @@
                                 getPackageName(), si.getId()));
                         if (disableDynamicWithId(si.getId(), /*ignoreInvisible*/ false,
                                 ShortcutInfo.DISABLED_REASON_APP_CHANGED) != null) {
-                            continue; // Actually removed.
+                            return false; // Actually removed.
                         }
                         // Still pinned, so fall-through and possibly update the resources.
                     }
                 }
 
-                if (si.hasAnyResources()) {
-                    if (publisherRes == null) {
-                        publisherRes = getPackageResources();
-                        if (publisherRes == null) {
-                            break; // Resources couldn't be loaded.
-                        }
-                    }
-
-                    final Resources res = publisherRes;
-                    mutateShortcut(si.getId(), si, shortcut -> {
-                        if (!shortcut.isOriginallyFromManifest()) {
-                            shortcut.lookupAndFillInResourceIds(res);
-                        }
-
-                        // If this shortcut is not from a manifest, then update all resource IDs
-                        // from resource names.  (We don't allow resource strings for
-                        // non-manifest at the moment, but icons can still be resources.)
-                        shortcut.setTimestamp(s.injectCurrentTimeMillis());
-                    });
+                if (!si.hasAnyResources() || publisherRes == null) {
+                    return false;
                 }
-            }
+
+                if (!si.isOriginallyFromManifest()) {
+                    si.lookupAndFillInResourceIds(publisherRes);
+                }
+
+                // If this shortcut is not from a manifest, then update all resource IDs
+                // from resource names.  (We don't allow resource strings for
+                // non-manifest at the moment, but icons can still be resources.)
+                si.setTimestamp(s.injectCurrentTimeMillis());
+                return true;
+            });
         }
 
         // (Re-)publish manifest shortcut.
@@ -1133,17 +1120,12 @@
         boolean changed = false;
 
         // Keep the previous IDs.
-        ArraySet<String> toDisableList = null;
-        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
-            final ShortcutInfo si = mShortcuts.valueAt(i);
-
+        final ArraySet<String> toDisableList = new ArraySet<>(1);
+        forEachShortcut(si -> {
             if (si.isManifestShortcut()) {
-                if (toDisableList == null) {
-                    toDisableList = new ArraySet<>();
-                }
                 toDisableList.add(si.getId());
             }
-        }
+        });
 
         // Publish new ones.
         if (newManifestShortcutList != null) {
@@ -1156,7 +1138,7 @@
                 final boolean newDisabled = !newShortcut.isEnabled();
 
                 final String id = newShortcut.getId();
-                final ShortcutInfo oldShortcut = mShortcuts.get(id);
+                final ShortcutInfo oldShortcut = findShortcutById(id);
 
                 boolean wasPinned = false;
 
@@ -1183,7 +1165,7 @@
                 // regardless.
                 forceReplaceShortcutInner(newShortcut); // This will clean up the old one too.
 
-                if (!newDisabled && toDisableList != null) {
+                if (!newDisabled && !toDisableList.isEmpty()) {
                     // Still alive, don't remove.
                     toDisableList.remove(id);
                 }
@@ -1191,7 +1173,7 @@
         }
 
         // Disable the previous manifest shortcuts that are no longer in the manifest.
-        if (toDisableList != null) {
+        if (!toDisableList.isEmpty()) {
             if (ShortcutService.DEBUG) {
                 Slog.d(TAG, String.format(
                         "Package %s: disabling %d stale shortcuts", getPackageName(),
@@ -1206,8 +1188,9 @@
                         /* overrideImmutable=*/ true, /*ignoreInvisible=*/ false,
                         ShortcutInfo.DISABLED_REASON_APP_CHANGED);
             }
-            removeOrphans();
         }
+        removeOrphans();
+
         adjustRanks();
         return changed;
     }
@@ -1279,25 +1262,21 @@
     private ArrayMap<ComponentName, ArrayList<ShortcutInfo>> sortShortcutsToActivities() {
         final ArrayMap<ComponentName, ArrayList<ShortcutInfo>> activitiesToShortcuts
                 = new ArrayMap<>();
-        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
-            final ShortcutInfo si = mShortcuts.valueAt(i);
+        forEachShortcut(si -> {
             if (si.isFloating()) {
-                continue; // Ignore floating shortcuts, which are not tied to any activities.
+                return; // Ignore floating shortcuts, which are not tied to any activities.
             }
 
             final ComponentName activity = si.getActivity();
             if (activity == null) {
                 mShortcutUser.mService.wtf("null activity detected.");
-                continue;
+                return;
             }
 
-            ArrayList<ShortcutInfo> list = activitiesToShortcuts.get(activity);
-            if (list == null) {
-                list = new ArrayList<>();
-                activitiesToShortcuts.put(activity, list);
-            }
+            ArrayList<ShortcutInfo> list = activitiesToShortcuts.computeIfAbsent(activity,
+                    k -> new ArrayList<>());
             list.add(si);
-        }
+        });
         return activitiesToShortcuts;
     }
 
@@ -1333,15 +1312,13 @@
         // (If it's for update, then don't count dynamic shortcuts, since they'll be replaced
         // anyway.)
         final ArrayMap<ComponentName, Integer> counts = new ArrayMap<>(4);
-        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
-            final ShortcutInfo shortcut = mShortcuts.valueAt(i);
-
+        forEachShortcut(shortcut -> {
             if (shortcut.isManifestShortcut()) {
                 incrementCountForActivity(counts, shortcut.getActivity(), 1);
             } else if (shortcut.isDynamic() && (operation != ShortcutService.OPERATION_SET)) {
                 incrementCountForActivity(counts, shortcut.getActivity(), 1);
             }
-        }
+        });
 
         for (int i = newList.size() - 1; i >= 0; i--) {
             final ShortcutInfo newShortcut = newList.get(i);
@@ -1354,7 +1331,7 @@
                 continue; // Activity can be null for update.
             }
 
-            final ShortcutInfo original = mShortcuts.get(newShortcut.getId());
+            final ShortcutInfo original = findShortcutById(newShortcut.getId());
             if (original == null) {
                 if (operation == ShortcutService.OPERATION_UPDATE) {
                     continue; // When updating, ignore if there's no target.
@@ -1391,34 +1368,19 @@
      * For all the text fields, refresh the string values if they're from resources.
      */
     public void resolveResourceStrings() {
-        // TODO: update resource strings in AppSearch
         final ShortcutService s = mShortcutUser.mService;
 
-        List<ShortcutInfo> changedShortcuts = null;
+        final Resources publisherRes = getPackageResources();
+        final List<ShortcutInfo> changedShortcuts = new ArrayList<>(1);
 
-        Resources publisherRes = null;
-        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
-            final ShortcutInfo si = mShortcuts.valueAt(i);
-
-            if (si.hasStringResources()) {
-                if (publisherRes == null) {
-                    publisherRes = getPackageResources();
-                    if (publisherRes == null) {
-                        break; // Resources couldn't be loaded.
-                    }
-                }
-
-                final Resources res = publisherRes;
-                mutateShortcut(si.getId(), si, shortcut -> {
-                    shortcut.resolveResourceStrings(res);
-                    shortcut.setTimestamp(s.injectCurrentTimeMillis());
-                });
-
-                if (changedShortcuts == null) {
-                    changedShortcuts = new ArrayList<>(1);
-                }
+        if (publisherRes != null) {
+            forEachShortcutMutateIf(si -> {
+                if (!si.hasStringResources()) return false;
+                si.resolveResourceStrings(publisherRes);
+                si.setTimestamp(s.injectCurrentTimeMillis());
                 changedShortcuts.add(si);
-            }
+                return true;
+            });
         }
         if (!CollectionUtils.isEmpty(changedShortcuts)) {
             s.packageShortcutsChanged(getPackageName(), getPackageUserId(), changedShortcuts, null);
@@ -1427,10 +1389,7 @@
 
     /** Clears the implicit ranks for all shortcuts. */
     public void clearAllImplicitRanks() {
-        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
-            final ShortcutInfo si = mShortcuts.valueAt(i);
-            mutateShortcut(si.getId(), si, ShortcutInfo::clearImplicitRankAndRankChangedFlag);
-        }
+        forEachShortcutMutate(ShortcutInfo::clearImplicitRankAndRankChangedFlag);
     }
 
     /**
@@ -1470,17 +1429,16 @@
         final long now = s.injectCurrentTimeMillis();
 
         // First, clear ranks for floating shortcuts.
-        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
-            final ShortcutInfo si = mShortcuts.valueAt(i);
+        forEachShortcutMutateIf(si -> {
             if (si.isFloating()) {
                 if (si.getRank() != 0) {
-                    mutateShortcut(si.getId(), si, shortcut -> {
-                        shortcut.setTimestamp(now);
-                        shortcut.setRank(0);
-                    });
+                    si.setTimestamp(now);
+                    si.setRank(0);
+                    return true;
                 }
             }
-        }
+            return false;
+        });
 
         // Then adjust ranks.  Ranks are unique for each activity, so we first need to sort
         // shortcuts to each activity.
@@ -1505,7 +1463,7 @@
                 }
                 // At this point, it must be dynamic.
                 if (!si.isDynamic()) {
-                    s.wtf("Non-dynamic shortcut found.");
+                    s.wtf("Non-dynamic shortcut found.:  " + si.toInsecureString());
                     continue;
                 }
                 final int thisRank = rank++;
@@ -1521,13 +1479,15 @@
 
     /** @return true if there's any shortcuts that are not manifest shortcuts. */
     public boolean hasNonManifestShortcuts() {
-        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
-            final ShortcutInfo si = mShortcuts.valueAt(i);
+        final boolean[] condition = new boolean[1];
+        forEachShortcutStopWhen(si -> {
             if (!si.isDeclaredInManifest()) {
+                condition[0] = true;
                 return true;
             }
-        }
-        return false;
+            return false;
+        });
+        return condition[0];
     }
 
     public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
@@ -1567,11 +1527,8 @@
 
         pw.print(prefix);
         pw.println("  Shortcuts:");
-        long totalBitmapSize = 0;
-        final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
-        final int size = shortcuts.size();
-        for (int i = 0; i < size; i++) {
-            final ShortcutInfo si = shortcuts.valueAt(i);
+        final long[] totalBitmapSize = new long[1];
+        forEachShortcut(si -> {
             pw.println(si.toDumpString(prefix + "    "));
             if (si.getBitmapPath() != null) {
                 final long len = new File(si.getBitmapPath()).length();
@@ -1580,15 +1537,15 @@
                 pw.print("bitmap size=");
                 pw.println(len);
 
-                totalBitmapSize += len;
+                totalBitmapSize[0] += len;
             }
-        }
+        });
         pw.print(prefix);
         pw.print("  ");
         pw.print("Total bitmap size: ");
-        pw.print(totalBitmapSize);
+        pw.print(totalBitmapSize[0]);
         pw.print(" (");
-        pw.print(Formatter.formatFileSize(mShortcutUser.mService.mContext, totalBitmapSize));
+        pw.print(Formatter.formatFileSize(mShortcutUser.mService.mContext, totalBitmapSize[0]));
         pw.println(")");
     }
 
@@ -1603,46 +1560,39 @@
                 | (matchManifest ? ShortcutInfo.FLAG_MANIFEST : 0)
                 | (matchCached ? ShortcutInfo.FLAG_CACHED_ALL : 0);
 
-        final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
-        final int size = shortcuts.size();
-        for (int i = 0; i < size; i++) {
-            final ShortcutInfo si = shortcuts.valueAt(i);
+        forEachShortcut(si -> {
             if ((si.getFlags() & shortcutFlags) != 0) {
                 pw.println(si.toDumpString(""));
             }
-        }
+        });
     }
 
     @Override
     public JSONObject dumpCheckin(boolean clear) throws JSONException {
         final JSONObject result = super.dumpCheckin(clear);
 
-        int numDynamic = 0;
-        int numPinned = 0;
-        int numManifest = 0;
-        int numBitmaps = 0;
-        long totalBitmapSize = 0;
+        final int[] numDynamic = new int[1];
+        final int[] numPinned = new int[1];
+        final int[] numManifest = new int[1];
+        final int[] numBitmaps = new int[1];
+        final long[] totalBitmapSize = new long[1];
 
-        final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
-        final int size = shortcuts.size();
-        for (int i = 0; i < size; i++) {
-            final ShortcutInfo si = shortcuts.valueAt(i);
-
-            if (si.isDynamic()) numDynamic++;
-            if (si.isDeclaredInManifest()) numManifest++;
-            if (si.isPinned()) numPinned++;
+        forEachShortcut(si -> {
+            if (si.isDynamic()) numDynamic[0]++;
+            if (si.isDeclaredInManifest()) numManifest[0]++;
+            if (si.isPinned()) numPinned[0]++;
 
             if (si.getBitmapPath() != null) {
-                numBitmaps++;
-                totalBitmapSize += new File(si.getBitmapPath()).length();
+                numBitmaps[0]++;
+                totalBitmapSize[0] += new File(si.getBitmapPath()).length();
             }
-        }
+        });
 
-        result.put(KEY_DYNAMIC, numDynamic);
-        result.put(KEY_MANIFEST, numManifest);
-        result.put(KEY_PINNED, numPinned);
-        result.put(KEY_BITMAPS, numBitmaps);
-        result.put(KEY_BITMAP_BYTES, totalBitmapSize);
+        result.put(KEY_DYNAMIC, numDynamic[0]);
+        result.put(KEY_MANIFEST, numManifest[0]);
+        result.put(KEY_PINNED, numPinned[0]);
+        result.put(KEY_BITMAPS, numBitmaps[0]);
+        result.put(KEY_BITMAP_BYTES, totalBitmapSize[0]);
 
         // TODO Log update frequency too.
 
@@ -1652,7 +1602,7 @@
     @Override
     public void saveToXml(@NonNull TypedXmlSerializer out, boolean forBackup)
             throws IOException, XmlPullParserException {
-        final int size = mShortcuts.size();
+        final int size = getShortcutCount();
         final int shareTargetSize = mShareTargets.size();
 
         if (size == 0 && shareTargetSize == 0 && mApiCallCount == 0) {
@@ -1666,9 +1616,15 @@
         ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
         getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup);
 
-        for (int j = 0; j < size; j++) {
-            saveShortcut(out, mShortcuts.valueAt(j), forBackup,
-                    getPackageInfo().isBackupAllowed());
+        if (forBackup) {
+            // Shortcuts are persisted in AppSearch, xml is only needed for backup.
+            forEachShortcut(si -> {
+                try {
+                    saveShortcut(out, si, forBackup, getPackageInfo().isBackupAllowed());
+                } catch (IOException | XmlPullParserException e) {
+                    throw new RuntimeException(e);
+                }
+            });
         }
 
         if (!forBackup) {
@@ -1785,12 +1741,14 @@
             }
             final Intent[] intentsNoExtras = si.getIntentsNoExtras();
             final PersistableBundle[] intentsExtras = si.getIntentPersistableExtrases();
-            final int numIntents = intentsNoExtras.length;
-            for (int i = 0; i < numIntents; i++) {
-                out.startTag(null, TAG_INTENT);
-                ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]);
-                ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]);
-                out.endTag(null, TAG_INTENT);
+            if (intentsNoExtras != null && intentsExtras != null) {
+                final int numIntents = intentsNoExtras.length;
+                for (int i = 0; i < numIntents; i++) {
+                    out.startTag(null, TAG_INTENT);
+                    ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]);
+                    ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]);
+                    out.endTag(null, TAG_INTENT);
+                }
             }
 
             ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras());
@@ -1877,9 +1835,8 @@
                     case TAG_SHORTCUT:
                         final ShortcutInfo si = parseShortcut(parser, packageName,
                                 shortcutUser.getUserId(), fromBackup);
-
                         // Don't use addShortcut(), we don't need to save the icon.
-                        ret.mShortcuts.put(si.getId(), si);
+                        ret.mShortcuts.add(si);
                         continue;
                     case TAG_SHARE_TARGET:
                         ret.mShareTargets.add(ShareTargetInfo.loadFromXml(parser));
@@ -2075,7 +2032,9 @@
 
     @VisibleForTesting
     List<ShortcutInfo> getAllShortcutsForTest() {
-        return new ArrayList<>(mShortcuts.values());
+        final List<ShortcutInfo> ret = new ArrayList<>(1);
+        forEachShortcut(ret::add);
+        return ret;
     }
 
     @VisibleForTesting
@@ -2087,7 +2046,7 @@
     public void verifyStates() {
         super.verifyStates();
 
-        boolean failed = false;
+        final boolean[] failed = new boolean[1];
 
         final ShortcutService s = mShortcutUser.mService;
 
@@ -2098,7 +2057,7 @@
         for (int outer = all.size() - 1; outer >= 0; outer--) {
             final ArrayList<ShortcutInfo> list = all.valueAt(outer);
             if (list.size() > mShortcutUser.mService.getMaxActivityShortcuts()) {
-                failed = true;
+                failed[0] = true;
                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": activity " + all.keyAt(outer)
                         + " has " + all.valueAt(outer).size() + " shortcuts.");
             }
@@ -2118,61 +2077,60 @@
         }
 
         // Verify each shortcut's status.
-        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
-            final ShortcutInfo si = mShortcuts.valueAt(i);
+        forEachShortcut(si -> {
             if (!(si.isDeclaredInManifest() || si.isDynamic() || si.isPinned() || si.isCached())) {
-                failed = true;
+                failed[0] = true;
                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
                         + " is not manifest, dynamic or pinned.");
             }
             if (si.isDeclaredInManifest() && si.isDynamic()) {
-                failed = true;
+                failed[0] = true;
                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
                         + " is both dynamic and manifest at the same time.");
             }
             if (si.getActivity() == null && !si.isFloating()) {
-                failed = true;
+                failed[0] = true;
                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
                         + " has null activity, but not floating.");
             }
             if ((si.isDynamic() || si.isManifestShortcut()) && !si.isEnabled()) {
-                failed = true;
+                failed[0] = true;
                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
                         + " is not floating, but is disabled.");
             }
             if (si.isFloating() && si.getRank() != 0) {
-                failed = true;
+                failed[0] = true;
                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
                         + " is floating, but has rank=" + si.getRank());
             }
             if (si.getIcon() != null) {
-                failed = true;
+                failed[0] = true;
                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
                         + " still has an icon");
             }
             if (si.hasAdaptiveBitmap() && !(si.hasIconFile() || si.hasIconUri())) {
-                failed = true;
+                failed[0] = true;
                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
                         + " has adaptive bitmap but was not saved to a file nor has icon uri.");
             }
             if (si.hasIconFile() && si.hasIconResource()) {
-                failed = true;
+                failed[0] = true;
                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
                         + " has both resource and bitmap icons");
             }
             if (si.hasIconFile() && si.hasIconUri()) {
-                failed = true;
+                failed[0] = true;
                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
                         + " has both url and bitmap icons");
             }
             if (si.hasIconUri() && si.hasIconResource()) {
-                failed = true;
+                failed[0] = true;
                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
                         + " has both url and resource icons");
             }
             if (si.isEnabled()
                     != (si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED)) {
-                failed = true;
+                failed[0] = true;
                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
                         + " isEnabled() and getDisabledReason() disagree: "
                         + si.isEnabled() + " vs " + si.getDisabledReason());
@@ -2180,18 +2138,18 @@
             if ((si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_VERSION_LOWER)
                     && (getPackageInfo().getBackupSourceVersionCode()
                     == ShortcutInfo.VERSION_CODE_UNKNOWN)) {
-                failed = true;
+                failed[0] = true;
                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
                         + " RESTORED_VERSION_LOWER with no backup source version code.");
             }
             if (s.isDummyMainActivity(si.getActivity())) {
-                failed = true;
+                failed[0] = true;
                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
                         + " has a dummy target activity");
             }
-        }
+        });
 
-        if (failed) {
+        if (failed[0]) {
             throw new IllegalStateException("See logcat for errors");
         }
     }
@@ -2202,7 +2160,7 @@
         } else {
             mPackageIdentifiers.remove(packageName);
         }
-        resetAppSearch(null);
+        resetAppSearch(session -> AndroidFuture.completedFuture(true));
     }
 
     void mutateShortcut(@NonNull final String id, @Nullable final ShortcutInfo shortcut,
@@ -2212,23 +2170,282 @@
         synchronized (mLock) {
             if (shortcut != null) {
                 transform.accept(shortcut);
-            } else {
-                transform.accept(findShortcutById(id));
             }
-            // TODO: Load ShortcutInfo from AppSearch, apply transformation logic and save
+            final ShortcutInfo si = getShortcutById(id);
+            if (si == null) {
+                return;
+            }
+            transform.accept(si);
+            saveShortcut(si);
         }
     }
 
+    private void saveShortcut(@NonNull final ShortcutInfo... shortcuts) {
+        Objects.requireNonNull(shortcuts);
+        saveShortcut(Arrays.asList(shortcuts));
+    }
+
+    private void saveShortcut(@NonNull final Collection<ShortcutInfo> shortcuts) {
+        Objects.requireNonNull(shortcuts);
+        ConcurrentUtils.waitForFutureNoInterrupt(
+                runInAppSearch(session -> {
+                    final AndroidFuture<Boolean> future = new AndroidFuture<>();
+                    session.put(new PutDocumentsRequest.Builder()
+                                    .addGenericDocuments(
+                                            AppSearchShortcutInfo.toGenericDocuments(shortcuts))
+                                    .build(),
+                            mShortcutUser.mExecutor,
+                            result -> {
+                                if (!result.isSuccess()) {
+                                    for (AppSearchResult<Void> k : result.getFailures().values()) {
+                                        Slog.e(TAG, k.getErrorMessage());
+                                    }
+                                    future.completeExceptionally(new RuntimeException(
+                                            "failed to save shortcuts"));
+                                    return;
+                                }
+                                future.complete(true);
+                            });
+                    return future;
+                }),
+                "saving shortcut");
+    }
+
     /**
      * Removes shortcuts from AppSearch.
      */
     void removeShortcuts() {
+        awaitInAppSearch("removing shortcuts", session -> {
+            final AndroidFuture<Boolean> future = new AndroidFuture<>();
+            session.remove("",
+                    new SearchSpec.Builder()
+                            .addFilterSchemas(AppSearchShortcutInfo.SCHEMA_TYPE)
+                            .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                            .build(),
+                    mShortcutUser.mExecutor, result -> {
+                        if (!result.isSuccess()) {
+                            future.completeExceptionally(new RuntimeException(
+                                    "Failed to cleanup shortcuts " + result.getErrorMessage()));
+                            return;
+                        }
+                        future.complete(true);
+                    });
+            return future;
+        });
+    }
+
+    private void removeShortcut(@NonNull final String id) {
+        Objects.requireNonNull(id);
+        awaitInAppSearch("removing shortcut with id=" + id, session -> {
+            final AndroidFuture<Boolean> future = new AndroidFuture<>();
+            session.remove(new RemoveByUriRequest.Builder(getPackageName()).addUris(id).build(),
+                    mShortcutUser.mExecutor, result -> {
+                        if (!result.isSuccess()) {
+                            final Map<String, AppSearchResult<Void>> failures =
+                                    result.getFailures();
+                            for (String key : failures.keySet()) {
+                                Slog.e(TAG, "Failed deleting " + key + ", error message:"
+                                        + failures.get(key).getErrorMessage());
+                            }
+                            future.completeExceptionally(new RuntimeException(
+                                    "Failed to delete shortcut: " + id));
+                            return;
+                        }
+                        future.complete(true);
+                    });
+            return future;
+        });
+    }
+
+    private ShortcutInfo getShortcutById(String id) {
+        return awaitInAppSearch("getting shortcut with id=" + id, session -> {
+            final AndroidFuture<ShortcutInfo> future = new AndroidFuture<>();
+            session.getByUri(
+                    new GetByUriRequest.Builder(getPackageName()).addUris(id).build(),
+                    mShortcutUser.mExecutor,
+                    results -> {
+                        if (results.isSuccess()) {
+                            Map<String, GenericDocument> documents = results.getSuccesses();
+                            for (GenericDocument doc : documents.values()) {
+                                final ShortcutInfo info = new AppSearchShortcutInfo(doc)
+                                        .toShortcutInfo(mShortcutUser.getUserId());
+                                future.complete(info);
+                                return;
+                            }
+                        }
+                        future.complete(null);
+                    });
+            return future;
+        });
+    }
+
+    private void forEachShortcut(
+            @NonNull final Consumer<ShortcutInfo> cb) {
+        forEachShortcutStopWhen(si -> {
+            cb.accept(si);
+            return false;
+        });
+    }
+
+    private void forEachShortcutMutate(@NonNull final Consumer<ShortcutInfo> cb) {
+        forEachShortcutMutateIf(si -> {
+            cb.accept(si);
+            return true;
+        });
+    }
+
+    private void forEachShortcutMutateIf(@NonNull final Function<ShortcutInfo, Boolean> cb) {
+        final SearchResults res = awaitInAppSearch("mutating shortcuts", session ->
+                AndroidFuture.completedFuture(session.search("", new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build())));
+        if (res == null) return;
+        List<ShortcutInfo> shortcuts = getNextPage(res);
+        while (!shortcuts.isEmpty()) {
+            final List<ShortcutInfo> changed = new ArrayList<>(1);
+            for (ShortcutInfo si : shortcuts) {
+                if (cb.apply(si)) changed.add(si);
+            }
+            saveShortcut(changed);
+            shortcuts = getNextPage(res);
+        }
+    }
+
+    private void forEachShortcutStopWhen(
+            @NonNull final Function<ShortcutInfo, Boolean> cb) {
+        forEachShortcutStopWhen("", cb);
+    }
+
+    private void forEachShortcutStopWhen(
+            @NonNull final String query, @NonNull final Function<ShortcutInfo, Boolean> cb) {
+        final SearchResults res = awaitInAppSearch("iterating shortcuts", session ->
+                AndroidFuture.completedFuture(session.search(query, new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build())));
+        if (res == null) return;
+        List<ShortcutInfo> shortcuts = getNextPage(res);
+        while (!shortcuts.isEmpty()) {
+            for (ShortcutInfo si : shortcuts) {
+                if (cb.apply(si)) return;
+            }
+            shortcuts = getNextPage(res);
+        }
+    }
+
+    private List<ShortcutInfo> getNextPage(@NonNull final SearchResults res) {
+        final AndroidFuture<List<ShortcutInfo>> future = new AndroidFuture<>();
+        final List<ShortcutInfo> ret = new ArrayList<>();
+        final long callingIdentity = Binder.clearCallingIdentity();
+        try {
+            res.getNextPage(mShortcutUser.mExecutor, nextPage -> {
+                if (!nextPage.isSuccess()) {
+                    future.complete(ret);
+                    return;
+                }
+                final List<SearchResult> results = nextPage.getResultValue();
+                if (results.isEmpty()) {
+                    future.complete(ret);
+                    return;
+                }
+                final List<ShortcutInfo> page = new ArrayList<>(results.size());
+                for (SearchResult result : results) {
+                    final ShortcutInfo si = new AppSearchShortcutInfo(result.getDocument())
+                            .toShortcutInfo(mShortcutUser.getUserId());
+                    page.add(si);
+                }
+                ret.addAll(page);
+                future.complete(ret);
+            });
+            return ConcurrentUtils.waitForFutureNoInterrupt(future,
+                    "getting next batch of shortcuts");
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
+    }
+
+    @Nullable
+    private <T> T awaitInAppSearch(
+            @NonNull final String description,
+            @NonNull final Function<AppSearchSession, CompletableFuture<T>> cb) {
+        return ConcurrentUtils.waitForFutureNoInterrupt(runInAppSearch(cb), description);
+    }
+
+    @Nullable
+    private <T> CompletableFuture<T> runInAppSearch(
+            @NonNull final Function<AppSearchSession, CompletableFuture<T>> cb) {
+        synchronized (mLock) {
+            final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+            try {
+                StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+                        .detectAll()
+                        .penaltyLog() // TODO: change this to penaltyDeath to fix the call-site
+                        .build());
+                if (mAppSearchSession != null) {
+                    final long callingIdentity = Binder.clearCallingIdentity();
+                    try {
+                        return AndroidFuture.supply(() -> mAppSearchSession).thenCompose(cb);
+                    } finally {
+                        Binder.restoreCallingIdentity(callingIdentity);
+                    }
+                } else {
+                    return resetAppSearch(cb);
+                }
+            } finally {
+                StrictMode.setThreadPolicy(oldPolicy);
+            }
+        }
+    }
+
+    private <T> CompletableFuture<T> resetAppSearch(
+            @NonNull final Function<AppSearchSession, CompletableFuture<T>> cb) {
+        final long callingIdentity = Binder.clearCallingIdentity();
+        final AppSearchManager.SearchContext searchContext =
+                new AppSearchManager.SearchContext.Builder()
+                        .setDatabaseName(getPackageName()).build();
+        final AppSearchSession session;
+        try {
+            session = ConcurrentUtils.waitForFutureNoInterrupt(
+                    mShortcutUser.getAppSearch(searchContext), "resetting app search");
+            ConcurrentUtils.waitForFutureNoInterrupt(setupSchema(session), "setting up schema");
+            mAppSearchSession = session;
+            return cb.apply(mAppSearchSession);
+        } catch (Exception e) {
+            return AndroidFuture.completedFuture(null);
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
+    }
+
+    @NonNull
+    private AndroidFuture<AppSearchSession> setupSchema(
+            @NonNull final AppSearchSession session) {
+        SetSchemaRequest.Builder schemaBuilder = new SetSchemaRequest.Builder()
+                .addSchemas(AppSearchPerson.SCHEMA, AppSearchShortcutInfo.SCHEMA);
+        for (PackageIdentifier pi : mPackageIdentifiers.values()) {
+            schemaBuilder = schemaBuilder
+                    .setSchemaTypeVisibilityForPackage(
+                            AppSearchPerson.SCHEMA_TYPE, true, pi)
+                    .setSchemaTypeVisibilityForPackage(
+                            AppSearchShortcutInfo.SCHEMA_TYPE, true, pi);
+        }
+        final AndroidFuture<AppSearchSession> future = new AndroidFuture<>();
+        session.setSchema(schemaBuilder.build(), mShortcutUser.mExecutor, result -> {
+            if (!result.isSuccess()) {
+                future.completeExceptionally(
+                        new IllegalArgumentException(result.getErrorMessage()));
+                return;
+            }
+            future.complete(session);
+        });
+        return future;
     }
 
     /**
      * Merge/replace shortcuts parsed from xml file.
      */
     void restoreParsedShortcuts(final boolean replace) {
+        if (replace) {
+            removeShortcuts();
+        }
+        saveShortcut(mShortcuts);
     }
 
     private boolean verifyRanksSequential(List<ShortcutInfo> list) {
@@ -2239,133 +2456,9 @@
             if (si.getRank() != i) {
                 failed = true;
                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
-                        + " rank=" + si.getRank() + " but expected to be "+ i);
+                        + " rank=" + si.getRank() + " but expected to be " + i);
             }
         }
         return failed;
     }
-
-    private void runInAppSearch(
-            Function<SearchSessionObservable, Consumer<AppSearchSession>>... observers) {
-        if (mShortcutUser == null) {
-            Slog.w(TAG, "shortcut user is null");
-            return;
-        }
-        synchronized (mLock) {
-            if (mAppSearchSession != null) {
-                final CountDownLatch latch = new CountDownLatch(1);
-                final long callingIdentity = Binder.clearCallingIdentity();
-                try {
-                    final SearchSessionObservable upstream =
-                            new SearchSessionObservable(mAppSearchSession, latch);
-                    for (Function<SearchSessionObservable, Consumer<AppSearchSession>> observer
-                            : observers) {
-                        upstream.map(observer);
-                    }
-                    upstream.next();
-                } finally {
-                    Binder.restoreCallingIdentity(callingIdentity);
-                }
-                ConcurrentUtils.waitForCountDownNoInterrupt(latch, 500,
-                        "timeout accessing shortcut");
-            } else {
-                resetAppSearch(observers);
-            }
-        }
-    }
-
-    private void resetAppSearch(
-            Function<SearchSessionObservable, Consumer<AppSearchSession>>... observers) {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final AppSearchManager.SearchContext searchContext =
-                new AppSearchManager.SearchContext.Builder()
-                        .setDatabaseName(getPackageName()).build();
-        mShortcutUser.runInAppSearch(searchContext, result -> {
-            if (!result.isSuccess()) {
-                Slog.e(TAG, "error getting search session during lazy init, "
-                        + result.getErrorMessage());
-                latch.countDown();
-                return;
-            }
-            // TODO: Flatten callback chain with proper async framework
-            final SearchSessionObservable upstream =
-                    new SearchSessionObservable(result.getResultValue(), latch)
-                            .map(this::setupSchema);
-            if (observers != null) {
-                for (Function<SearchSessionObservable, Consumer<AppSearchSession>> observer
-                        : observers) {
-                    upstream.map(observer);
-                }
-            }
-            upstream.map(observable -> session -> {
-                mAppSearchSession = session;
-                observable.next();
-            });
-            upstream.next();
-        });
-        ConcurrentUtils.waitForCountDownNoInterrupt(latch, 1500,
-                "timeout accessing shortcut during lazy initialization");
-    }
-
-    /**
-     * creates the schema for shortcut in the database
-     */
-    private Consumer<AppSearchSession> setupSchema(SearchSessionObservable observable) {
-        return session -> {
-            SetSchemaRequest.Builder schemaBuilder = new SetSchemaRequest.Builder()
-                            .addSchemas(AppSearchPerson.SCHEMA, AppSearchShortcutInfo.SCHEMA);
-            for (PackageIdentifier pi : mPackageIdentifiers.values()) {
-                schemaBuilder = schemaBuilder
-                        .setSchemaTypeVisibilityForPackage(
-                                AppSearchPerson.SCHEMA_TYPE, true, pi)
-                        .setSchemaTypeVisibilityForPackage(
-                                AppSearchShortcutInfo.SCHEMA_TYPE, true, pi);
-            }
-            session.setSchema(schemaBuilder.build(), mShortcutUser.mExecutor, result -> {
-                if (!result.isSuccess()) {
-                    observable.error("failed to instantiate app search schema: "
-                            + result.getErrorMessage());
-                    return;
-                }
-                observable.next();
-            });
-        };
-    }
-
-    /**
-     * TODO: Replace this temporary implementation with proper async framework
-     */
-    private class SearchSessionObservable {
-
-        final AppSearchSession mSession;
-        final CountDownLatch mLatch;
-        final ArrayList<Consumer<AppSearchSession>> mObservers = new ArrayList<>(1);
-
-        SearchSessionObservable(@NonNull final AppSearchSession session,
-                @NonNull final CountDownLatch latch) {
-            mSession = session;
-            mLatch = latch;
-        }
-
-        SearchSessionObservable map(
-                Function<SearchSessionObservable, Consumer<AppSearchSession>> observer) {
-            mObservers.add(observer.apply(this));
-            return this;
-        }
-
-        void next() {
-            if (mObservers.isEmpty()) {
-                mLatch.countDown();
-                return;
-            }
-            mObservers.remove(0).accept(mSession);
-        }
-
-        void error(@Nullable final String errorMessage) {
-            if (errorMessage != null) {
-                Slog.e(TAG, errorMessage);
-            }
-            mLatch.countDown();
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 38cba4c..fc88f39 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2590,7 +2590,6 @@
 
         final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
         ps.findAll(ret, query, cloneFlags);
-
         return new ParceledListSlice<>(setReturnedByServer(ret));
     }
 
@@ -5078,6 +5077,17 @@
     }
 
     @VisibleForTesting
+    void updatePackageShortcutForTest(String packageName, String shortcutId, int userId,
+            Consumer<ShortcutInfo> cb) {
+        synchronized (mLock) {
+            final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId);
+            if (pkg == null) return;
+
+            pkg.mutateShortcut(shortcutId, null, cb);
+        }
+    }
+
+    @VisibleForTesting
     ShortcutLauncher getLauncherShortcutForTest(String packageName, int userId) {
         synchronized (mLock) {
             final ShortcutUser user = mUsers.get(userId);
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index ec784d0..51cb995 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -19,7 +19,6 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.appsearch.AppSearchManager;
-import android.app.appsearch.AppSearchResult;
 import android.app.appsearch.AppSearchSession;
 import android.content.pm.ShortcutManager;
 import android.metrics.LogMaker;
@@ -35,6 +34,7 @@
 import android.util.TypedXmlSerializer;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.server.FgThread;
@@ -715,17 +715,26 @@
                 .setSubtype(totalSharingShortcutCount));
     }
 
-    void runInAppSearch(@NonNull final AppSearchManager.SearchContext searchContext,
-            @NonNull final Consumer<AppSearchResult<AppSearchSession>> callback) {
+    AndroidFuture<AppSearchSession> getAppSearch(
+            @NonNull final AppSearchManager.SearchContext searchContext) {
+        final AndroidFuture<AppSearchSession> future = new AndroidFuture<>();
         if (mAppSearchManager == null) {
-            Slog.e(TAG, "app search manager is null");
-            return;
+            future.completeExceptionally(new RuntimeException("app search manager is null"));
+            return future;
         }
         final long callingIdentity = Binder.clearCallingIdentity();
         try {
-            mAppSearchManager.createSearchSession(searchContext, mExecutor, callback);
+            mAppSearchManager.createSearchSession(searchContext, mExecutor, result -> {
+                if (!result.isSuccess()) {
+                    future.completeExceptionally(
+                            new RuntimeException(result.getErrorMessage()));
+                    return;
+                }
+                future.complete(result.getResultValue());
+            });
         } finally {
             Binder.restoreCallingIdentity(callingIdentity);
         }
+        return future;
     }
 }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index fe19956..2a0257d 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1507,6 +1507,26 @@
     }
 
     @Override
+    public boolean isCloneProfile(@UserIdInt int userId) {
+        checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isCloneProfile");
+        synchronized (mUsersLock) {
+            UserInfo userInfo = getUserInfoLU(userId);
+            return userInfo != null && userInfo.isCloneProfile();
+        }
+    }
+
+    @Override
+    public boolean sharesMediaWithParent(@UserIdInt int userId) {
+        checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId,
+                "sharesMediaWithParent");
+        synchronized (mUsersLock) {
+            UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId);
+            return userTypeDetails != null ? userTypeDetails.isProfile()
+                    && userTypeDetails.sharesMediaWithParent() : false;
+        }
+    }
+
+    @Override
     public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
         checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId,
                 "isUserUnlockingOrUnlocked");
diff --git a/services/core/java/com/android/server/pm/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java
index 17ce386..6824f7d 100644
--- a/services/core/java/com/android/server/pm/UserTypeDetails.java
+++ b/services/core/java/com/android/server/pm/UserTypeDetails.java
@@ -149,6 +149,13 @@
      */
     private final @Nullable int[] mDarkThemeBadgeColors;
 
+    /**
+     * Denotes if the user shares media with its parent user.
+     *
+     * <p> Default value is false
+     */
+    private final boolean mSharesMediaWithParent;
+
     private UserTypeDetails(@NonNull String name, boolean enabled, int maxAllowed,
             @UserInfoFlag int baseType, @UserInfoFlag int defaultUserInfoPropertyFlags, int label,
             int maxAllowedPerParent,
@@ -158,7 +165,8 @@
             @Nullable Bundle defaultRestrictions,
             @Nullable Bundle defaultSystemSettings,
             @Nullable Bundle defaultSecureSettings,
-            @Nullable List<DefaultCrossProfileIntentFilter> defaultCrossProfileIntentFilters) {
+            @Nullable List<DefaultCrossProfileIntentFilter> defaultCrossProfileIntentFilters,
+            boolean sharesMediaWithParent) {
         this.mName = name;
         this.mEnabled = enabled;
         this.mMaxAllowed = maxAllowed;
@@ -177,6 +185,7 @@
         this.mBadgeLabels = badgeLabels;
         this.mBadgeColors = badgeColors;
         this.mDarkThemeBadgeColors = darkThemeBadgeColors;
+        this.mSharesMediaWithParent = sharesMediaWithParent;
     }
 
     /**
@@ -291,6 +300,13 @@
         return (mBaseType & UserInfo.FLAG_SYSTEM) != 0;
     }
 
+    /**
+     * Returns true if the user has shared media with parent user or false otherwise.
+     */
+    public boolean sharesMediaWithParent() {
+        return mSharesMediaWithParent;
+    }
+
     /** Returns a {@link Bundle} representing the default user restrictions. */
     @NonNull Bundle getDefaultRestrictions() {
         return BundleUtils.clone(mDefaultRestrictions);
@@ -318,7 +334,6 @@
                 : Collections.emptyList();
     }
 
-
     /** Dumps details of the UserTypeDetails. Do not parse this. */
     public void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("mName: "); pw.println(mName);
@@ -383,6 +398,7 @@
         private @DrawableRes int mIconBadge = Resources.ID_NULL;
         private @DrawableRes int mBadgePlain = Resources.ID_NULL;
         private @DrawableRes int mBadgeNoBackground = Resources.ID_NULL;
+        private boolean mSharesMediaWithParent = false;
 
         public Builder setName(String name) {
             mName = name;
@@ -473,6 +489,15 @@
             return this;
         }
 
+        /**
+         * Sets shared media property for the user.
+         * @param sharesMediaWithParent the value to be set, true or false
+         */
+        public Builder setSharesMediaWithParent(boolean sharesMediaWithParent) {
+            mSharesMediaWithParent = sharesMediaWithParent;
+            return this;
+        }
+
         @UserInfoFlag int getBaseType() {
             return mBaseType;
         }
@@ -502,7 +527,7 @@
                     mIconBadge, mBadgePlain, mBadgeNoBackground, mBadgeLabels, mBadgeColors,
                     mDarkThemeBadgeColors == null ? mBadgeColors : mDarkThemeBadgeColors,
                     mDefaultRestrictions, mDefaultSystemSettings, mDefaultSecureSettings,
-                    mDefaultCrossProfileIntentFilters);
+                    mDefaultCrossProfileIntentFilters, mSharesMediaWithParent);
         }
 
         private boolean hasBadge() {
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 6aac0b2..e8421a5 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -29,6 +29,7 @@
 import static android.os.UserManager.USER_TYPE_FULL_RESTRICTED;
 import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
 import static android.os.UserManager.USER_TYPE_FULL_SYSTEM;
+import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
 import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
 import static android.os.UserManager.USER_TYPE_PROFILE_TEST;
 import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS;
@@ -100,6 +101,7 @@
         builders.put(USER_TYPE_FULL_DEMO, getDefaultTypeFullDemo());
         builders.put(USER_TYPE_FULL_RESTRICTED, getDefaultTypeFullRestricted());
         builders.put(USER_TYPE_SYSTEM_HEADLESS, getDefaultTypeSystemHeadless());
+        builders.put(USER_TYPE_PROFILE_CLONE, getDefaultTypeProfileClone());
         if (Build.IS_DEBUGGABLE) {
             builders.put(USER_TYPE_PROFILE_TEST, getDefaultTypeProfileTest());
         }
@@ -108,6 +110,21 @@
     }
 
     /**
+     * Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_CLONE}
+     * configuration.
+     */
+    // TODO(b/182396009): Add default restrictions, if needed for clone user type.
+    private static UserTypeDetails.Builder getDefaultTypeProfileClone() {
+        return new UserTypeDetails.Builder()
+                .setName(USER_TYPE_PROFILE_CLONE)
+                .setBaseType(FLAG_PROFILE)
+                .setMaxAllowedPerParent(1)
+                .setLabel(0)
+                .setDefaultRestrictions(null)
+                .setSharesMediaWithParent(true);
+    }
+
+    /**
      * Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_MANAGED}
      * configuration.
      */
diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java
index b421cfc..cda4806 100644
--- a/services/core/java/com/android/server/pm/permission/Permission.java
+++ b/services/core/java/com/android/server/pm/permission/Permission.java
@@ -435,11 +435,12 @@
                 }
             }
         }
+        boolean wasNonRuntime = permission != null && permission.mType != TYPE_CONFIG
+                && !permission.isRuntime();
         if (permission == null) {
             permission = new Permission(permissionInfo.name, permissionInfo.packageName,
                     TYPE_MANIFEST);
         }
-        boolean wasNonRuntime = !permission.isRuntime();
         StringBuilder r = null;
         if (!permission.mReconciled) {
             if (permission.mPermissionInfo.packageName == null
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 616058f..3bb5c16 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -2937,7 +2937,8 @@
                 >= Build.VERSION_CODES.M;
 
         for (String permission : ps.getGrantedPermissions()) {
-            if (!pkg.getImplicitPermissions().contains(permission)) {
+            if (pkg.getRequestedPermissions().contains(permission)
+                    && !pkg.getImplicitPermissions().contains(permission)) {
                 Permission bp = mRegistry.getPermission(permission);
                 if (bp != null && bp.isRuntime()) {
                     int flags = ps.getPermissionFlags(permission);
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
index cb3b5c9..44ff3eb 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
@@ -22,7 +22,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.Binder;
 
 import com.android.internal.util.CollectionUtils;
 import com.android.server.compat.PlatformCompat;
@@ -77,9 +76,7 @@
 
     static boolean isChangeEnabled(PlatformCompat platformCompat, AndroidPackage pkg,
             long changeId) {
-        //noinspection ConstantConditions
-        return Binder.withCleanCallingIdentity(
-                () -> platformCompat.isChangeEnabled(changeId, buildMockAppInfo(pkg)));
+        return  platformCompat.isChangeEnabledInternalNoLogging(changeId, buildMockAppInfo(pkg));
     }
 
     /**
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index d0aa28b..1b5bb95 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2341,8 +2341,6 @@
             params.packageName = packageName;
             params.windowAnimations = win.getWindowStyle().getResourceId(
                     com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
-            params.privateFlags |=
-                    WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;
             params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
             // Setting as trusted overlay to let touches pass through. This is safe because this
             // window is controlled by the system.
diff --git a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
index a7f3cdb..c029bf5 100644
--- a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
+++ b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
@@ -28,6 +28,7 @@
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.content.Context;
+import android.hardware.SensorPrivacyManager;
 import android.os.Binder;
 import android.os.CancellationSignal;
 import android.os.ResultReceiver;
@@ -79,6 +80,7 @@
             FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__FAILURE;
 
     private final Context mContext;
+    private final SensorPrivacyManager mPrivacyManager;
     boolean mIsServiceEnabled;
 
     public RotationResolverManagerService(Context context) {
@@ -89,6 +91,7 @@
                 PACKAGE_UPDATE_POLICY_REFRESH_EAGER
                         | /*To avoid high rotation latency*/ PACKAGE_RESTART_POLICY_REFRESH_EAGER);
         mContext = context;
+        mPrivacyManager = SensorPrivacyManager.getInstance(context);
     }
 
     @Override
@@ -156,15 +159,22 @@
             Objects.requireNonNull(callbackInternal);
             Objects.requireNonNull(cancellationSignalInternal);
             synchronized (mLock) {
-                if (mIsServiceEnabled) {
-                    final RotationResolverManagerPerUserService service = getServiceForUserLocked(
-                            UserHandle.getCallingUserId());
+                final boolean isCameraAvailable = !mPrivacyManager.isSensorPrivacyEnabled(
+                        SensorPrivacyManager.Sensors.CAMERA);
+                if (mIsServiceEnabled && isCameraAvailable) {
+                    final RotationResolverManagerPerUserService service =
+                            getServiceForUserLocked(
+                                    UserHandle.getCallingUserId());
                     final RotationResolutionRequest request = new RotationResolutionRequest("",
                             currentRotation, proposedRotation, true, timeout);
                     service.resolveRotationLocked(callbackInternal, request,
                             cancellationSignalInternal);
                 } else {
-                    Slog.w(TAG, "Rotation Resolver service is disabled.");
+                    if (isCameraAvailable) {
+                        Slog.w(TAG, "Rotation Resolver service is disabled.");
+                    } else {
+                        Slog.w(TAG, "Camera is locked by a toggle.");
+                    }
                     callbackInternal.onFailure(ROTATION_RESULT_FAILURE_CANCELLED);
                     logRotationStats(proposedRotation, currentRotation, RESOLUTION_DISABLED);
                 }
diff --git a/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java b/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java
new file mode 100644
index 0000000..3ca8a5a
--- /dev/null
+++ b/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java
@@ -0,0 +1,302 @@
+/*
+ * 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.servicewatcher;
+
+import static android.content.pm.PackageManager.GET_META_DATA;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.UserHandle.USER_SYSTEM;
+
+import android.annotation.BoolRes;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.app.ActivityManagerInternal;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.permission.PermissionManager;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
+import com.android.server.LocalServices;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceChangedListener;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceSupplier;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Supplies services based on the current active user and version as defined in the service
+ * manifest. This implementation uses {@link android.content.pm.PackageManager#MATCH_SYSTEM_ONLY} to
+ * ensure only system (ie, privileged) services are matched. It also handles services that are not
+ * direct boot aware, and will automatically pick the best service as the user's direct boot state
+ * changes.
+ *
+ * <p>Optionally, two permissions may be specified: (1) a caller permission - any service that does
+ * not require callers to hold this permission is rejected (2) a service permission - any service
+ * whose package does not hold this permission is rejected.
+ */
+public class CurrentUserServiceSupplier extends BroadcastReceiver implements
+        ServiceSupplier<CurrentUserServiceSupplier.BoundServiceInfo> {
+
+    private static final String TAG = "CurrentUserServiceSupplier";
+
+    private static final String EXTRA_SERVICE_VERSION = "serviceVersion";
+    private static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
+
+    private static final Comparator<BoundServiceInfo> sBoundServiceInfoComparator = (o1, o2) -> {
+        if (o1 == o2) {
+            return 0;
+        } else if (o1 == null) {
+            return -1;
+        } else if (o2 == null) {
+            return 1;
+        }
+
+        // ServiceInfos with higher version numbers always win. if version numbers are equal
+        // then we prefer components that work for all users vs components that only work for a
+        // single user at a time. otherwise everything's equal.
+        int ret = Integer.compare(o1.getVersion(), o2.getVersion());
+        if (ret == 0) {
+            if (o1.getUserId() != USER_SYSTEM && o2.getUserId() == USER_SYSTEM) {
+                ret = -1;
+            } else if (o1.getUserId() == USER_SYSTEM && o2.getUserId() != USER_SYSTEM) {
+                ret = 1;
+            }
+        }
+        return ret;
+    };
+
+    /** Bound service information with version information. */
+    public static class BoundServiceInfo extends ServiceWatcher.BoundServiceInfo {
+
+        private static int parseUid(ResolveInfo resolveInfo) {
+            int uid = resolveInfo.serviceInfo.applicationInfo.uid;
+            Bundle metadata = resolveInfo.serviceInfo.metaData;
+            if (metadata != null && metadata.getBoolean(EXTRA_SERVICE_IS_MULTIUSER, false)) {
+                // reconstruct a uid for the same app but with the system user - hope this exists
+                uid = UserHandle.getUid(USER_SYSTEM, UserHandle.getAppId(uid));
+            }
+            return uid;
+        }
+
+        private static int parseVersion(ResolveInfo resolveInfo) {
+            int version = Integer.MIN_VALUE;
+            if (resolveInfo.serviceInfo.metaData != null) {
+                version = resolveInfo.serviceInfo.metaData.getInt(EXTRA_SERVICE_VERSION, version);
+            }
+            return version;
+        }
+
+        private final int mVersion;
+        private final @Nullable Bundle mMetadata;
+
+        protected BoundServiceInfo(String action, ResolveInfo resolveInfo) {
+            this(action, parseUid(resolveInfo), resolveInfo.serviceInfo.getComponentName(),
+                    parseVersion(resolveInfo), resolveInfo.serviceInfo.metaData);
+        }
+
+        protected BoundServiceInfo(String action, int uid, ComponentName componentName, int version,
+                @Nullable Bundle metadata) {
+            super(action, uid, componentName);
+
+            mVersion = version;
+            mMetadata = metadata;
+        }
+
+        public int getVersion() {
+            return mVersion;
+        }
+
+        public @Nullable Bundle getMetadata() {
+            return mMetadata;
+        }
+
+        @Override
+        public String toString() {
+            return super.toString() + "@" + mVersion;
+        }
+    }
+
+    private static @Nullable String retrieveExplicitPackage(Context context,
+            @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
+        Resources resources = context.getResources();
+        boolean enableOverlay = resources.getBoolean(enableOverlayResId);
+        if (!enableOverlay) {
+            return resources.getString(nonOverlayPackageResId);
+        } else {
+            return null;
+        }
+    }
+
+    private final Context mContext;
+    private final ActivityManagerInternal mActivityManager;
+    private final Intent mIntent;
+    // a permission that the service forces callers (ie ServiceWatcher/system server) to hold
+    private final @Nullable String mCallerPermission;
+    // a permission that the service package should hold
+    private final @Nullable String mServicePermission;
+
+    private volatile ServiceChangedListener mListener;
+
+    public CurrentUserServiceSupplier(Context context, String action) {
+        this(context, action, null, null, null);
+    }
+
+    public CurrentUserServiceSupplier(Context context, String action,
+            @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
+        this(context, action,
+                retrieveExplicitPackage(context, enableOverlayResId, nonOverlayPackageResId), null,
+                null);
+    }
+
+    public CurrentUserServiceSupplier(Context context, String action,
+            @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId,
+            @Nullable String callerPermission, @Nullable String servicePermission) {
+        this(context, action,
+                retrieveExplicitPackage(context, enableOverlayResId, nonOverlayPackageResId),
+                callerPermission, servicePermission);
+    }
+
+    public CurrentUserServiceSupplier(Context context, String action,
+            @Nullable String explicitPackage, @Nullable String callerPermission,
+            @Nullable String servicePermission) {
+        mContext = context;
+        mActivityManager = Objects.requireNonNull(
+                LocalServices.getService(ActivityManagerInternal.class));
+        mIntent = new Intent(action);
+
+        if (explicitPackage != null) {
+            mIntent.setPackage(explicitPackage);
+        }
+
+        mCallerPermission = callerPermission;
+        mServicePermission = servicePermission;
+    }
+
+    @Override
+    public boolean hasMatchingService() {
+        List<ResolveInfo> resolveInfos = mContext.getPackageManager()
+                .queryIntentServicesAsUser(mIntent,
+                        MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_SYSTEM_ONLY,
+                        UserHandle.USER_SYSTEM);
+        return !resolveInfos.isEmpty();
+    }
+
+    @Override
+    public void register(ServiceChangedListener listener) {
+        Preconditions.checkState(mListener == null);
+
+        mListener = listener;
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+        intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
+        mContext.registerReceiverAsUser(this, UserHandle.ALL, intentFilter, null,
+                FgThread.getHandler());
+    }
+
+    @Override
+    public void unregister() {
+        Preconditions.checkArgument(mListener != null);
+
+        mListener = null;
+        mContext.unregisterReceiver(this);
+    }
+
+    @Override
+    public BoundServiceInfo getServiceInfo() {
+        BoundServiceInfo bestServiceInfo = null;
+
+        // only allow privileged services in the correct direct boot state to match
+        int currentUserId = mActivityManager.getCurrentUserId();
+        List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser(
+                mIntent,
+                GET_META_DATA | MATCH_DIRECT_BOOT_AUTO | MATCH_SYSTEM_ONLY,
+                currentUserId);
+        for (ResolveInfo resolveInfo : resolveInfos) {
+            ServiceInfo service = Objects.requireNonNull(resolveInfo.serviceInfo);
+
+            if (mCallerPermission != null) {
+                if (!mCallerPermission.equals(service.permission)) {
+                    Log.d(TAG, service.getComponentName().flattenToShortString()
+                            + " disqualified due to not requiring " + mCallerPermission);
+                    continue;
+                }
+            }
+
+            BoundServiceInfo serviceInfo = new BoundServiceInfo(mIntent.getAction(), resolveInfo);
+
+            if (mServicePermission != null) {
+                if (PermissionManager.checkPackageNamePermission(mServicePermission,
+                        service.packageName, serviceInfo.getUserId()) != PERMISSION_GRANTED) {
+                    Log.d(TAG, serviceInfo.getComponentName().flattenToShortString()
+                            + " disqualified due to not holding " + mCallerPermission);
+                    continue;
+                }
+            }
+
+            if (sBoundServiceInfoComparator.compare(serviceInfo, bestServiceInfo) > 0) {
+                bestServiceInfo = serviceInfo;
+            }
+        }
+
+        return bestServiceInfo;
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        if (action == null) {
+            return;
+        }
+        int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+        if (userId == UserHandle.USER_NULL) {
+            return;
+        }
+        ServiceChangedListener listener = mListener;
+        if (listener == null) {
+            return;
+        }
+
+        switch (action) {
+            case Intent.ACTION_USER_SWITCHED:
+                listener.onServiceChanged();
+                break;
+            case Intent.ACTION_USER_UNLOCKED:
+                // user unlocked implies direct boot mode may have changed
+                if (userId == mActivityManager.getCurrentUserId()) {
+                    listener.onServiceChanged();
+                }
+                break;
+            default:
+                break;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
index 5d49663..23b5d98 100644
--- a/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * 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.
@@ -16,516 +16,244 @@
 
 package com.android.server.servicewatcher;
 
-import static android.content.Context.BIND_AUTO_CREATE;
-import static android.content.Context.BIND_NOT_FOREGROUND;
-import static android.content.Context.BIND_NOT_VISIBLE;
-import static android.content.pm.PackageManager.GET_META_DATA;
-import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
-import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
-import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
-import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
-
-import android.annotation.BoolRes;
-import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.StringRes;
 import android.annotation.UserIdInt;
-import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.ServiceConnection;
 import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.Log;
 
-import com.android.internal.annotations.Immutable;
-import com.android.internal.content.PackageMonitor;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.FgThread;
 
-import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.List;
-import java.util.Map;
 import java.util.Objects;
-import java.util.function.Predicate;
 
 /**
- * Maintains a binding to the best service that matches the given intent information. Bind and
- * unbind callbacks, as well as all binder operations, will all be run on a single thread.
+ * A ServiceWatcher is responsible for continuously maintaining an active binding to a service
+ * selected by it's {@link ServiceSupplier}. The {@link ServiceSupplier} may change the service it
+ * selects over time, and the currently bound service may crash, restart, have a user change, have
+ * changes made to its package, and so on and so forth. The ServiceWatcher is responsible for
+ * maintaining the binding across all these changes.
+ *
+ * <p>Clients may invoke {@link BinderOperation}s on the ServiceWatcher, and it will make a best
+ * effort to run these on the currently bound service, but individual operations may fail (if there
+ * is no service currently bound for instance). In order to help clients maintain the correct state,
+ * clients may supply a {@link ServiceListener}, which is informed when the ServiceWatcher connects
+ * and disconnects from a service. This allows clients to bring a bound service back into a known
+ * state on connection, and then run binder operations from there. In order to help clients
+ * accomplish this, ServiceWatcher guarantees that {@link BinderOperation}s and the
+ * {@link ServiceListener} will always be run on the same thread, so that strong ordering guarantees
+ * can be established between them.
+ *
+ * There is never any guarantee of whether a ServiceWatcher is currently connected to a service, and
+ * whether any particular {@link BinderOperation} will succeed. Clients must ensure they do not rely
+ * on this, and instead use {@link ServiceListener} notifications as necessary to recover from
+ * failures.
  */
-public class ServiceWatcher implements ServiceConnection {
+public interface ServiceWatcher {
 
-    private static final String TAG = "ServiceWatcher";
-    private static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
-
-    private static final String EXTRA_SERVICE_VERSION = "serviceVersion";
-    private static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
-
-    private static final long RETRY_DELAY_MS = 15 * 1000;
-
-    private static final Predicate<ResolveInfo> DEFAULT_SERVICE_CHECK_PREDICATE = x -> true;
-
-    /** Function to run on binder interface. */
-    public interface BinderRunner {
-        /** Called to run client code with the binder. */
+    /**
+     * Operation to run on a binder interface. All operations will be run on the thread used by the
+     * ServiceWatcher this is run with.
+     */
+    interface BinderOperation {
+        /** Invoked to run the operation. Run on the ServiceWatcher thread. */
         void run(IBinder binder) throws RemoteException;
+
         /**
-         * Called if an error occurred and the function could not be run. This callback is only
-         * intended for resource deallocation and cleanup in response to a single binder operation,
-         * it should not be used to propagate errors further.
+         * Invoked if {@link #run(IBinder)} could not be invoked because there was no current
+         * binding, or if {@link #run(IBinder)} threw an exception ({@link RemoteException} or
+         * {@link RuntimeException}). This callback is only intended for resource deallocation and
+         * cleanup in response to a single binder operation, it should not be used to propagate
+         * errors further. Run on the ServiceWatcher thread.
          */
         default void onError() {}
     }
 
-    /** Function to run on binder interface when first bound. */
-    public interface OnBindRunner {
-        /** Called to run client code with the binder. */
-        void run(IBinder binder, BoundService service) throws RemoteException;
+    /**
+     * Listener for bind and unbind events. All operations will be run on the thread used by the
+     * ServiceWatcher this is run with.
+     *
+     * @param <TBoundServiceInfo> type of bound service
+     */
+    interface ServiceListener<TBoundServiceInfo extends BoundServiceInfo> {
+        /** Invoked when a service is bound. Run on the ServiceWatcher thread. */
+        void onBind(IBinder binder, TBoundServiceInfo service) throws RemoteException;
+
+        /** Invoked when a service is unbound. Run on the ServiceWatcher thread. */
+        void onUnbind();
     }
 
     /**
-     * Information on the service ServiceWatcher has selected as the best option for binding.
+     * A listener for when a {@link ServiceSupplier} decides that the current service has changed.
      */
-    @Immutable
-    public static final class BoundService implements Comparable<BoundService> {
+    interface ServiceChangedListener {
+        /**
+         * Should be invoked when the current service may have changed.
+         */
+        void onServiceChanged();
+    }
 
-        public static final BoundService NONE = new BoundService(Integer.MIN_VALUE, null,
-                false, null, -1);
+    /**
+     * This supplier encapsulates the logic of deciding what service a {@link ServiceWatcher} should
+     * be bound to at any given moment.
+     *
+     * @param <TBoundServiceInfo> type of bound service
+     */
+    interface ServiceSupplier<TBoundServiceInfo extends BoundServiceInfo> {
+        /**
+         * Should return true if there exists at least one service capable of meeting the criteria
+         * of this supplier. This does not imply that {@link #getServiceInfo()} will always return a
+         * non-null result, as any service may be disqualified for various reasons at any point in
+         * time. May be invoked at any time from any thread and thus should generally not have any
+         * dependency on the other methods in this interface.
+         */
+        boolean hasMatchingService();
 
-        public final int version;
-        @Nullable
-        public final ComponentName component;
-        public final boolean serviceIsMultiuser;
-        public final int uid;
-        @Nullable
-        public final Bundle metadata;
+        /**
+         * Invoked when the supplier should start monitoring for any changes that could result in a
+         * different service selection, and should invoke
+         * {@link ServiceChangedListener#onServiceChanged()} in that case. {@link #getServiceInfo()}
+         * may be invoked after this method is called.
+         */
+        void register(ServiceChangedListener listener);
 
-        BoundService(ResolveInfo resolveInfo) {
-            Preconditions.checkArgument(resolveInfo.serviceInfo.getComponentName() != null);
+        /**
+         * Invoked when the supplier should stop monitoring for any changes that could result in a
+         * different service selection, should no longer invoke
+         * {@link ServiceChangedListener#onServiceChanged()}. {@link #getServiceInfo()} will not be
+         * invoked after this method is called.
+         */
+        void unregister();
 
-            metadata = resolveInfo.serviceInfo.metaData;
-            if (metadata != null) {
-                version = metadata.getInt(EXTRA_SERVICE_VERSION, Integer.MIN_VALUE);
-                serviceIsMultiuser = metadata.getBoolean(EXTRA_SERVICE_IS_MULTIUSER, false);
-            } else {
-                version = Integer.MIN_VALUE;
-                serviceIsMultiuser = false;
-            }
+        /**
+         * Must be implemented to return the current service selected by this supplier. May return
+         * null if no service currently meets the criteria. Only invoked while registered.
+         */
+        @Nullable TBoundServiceInfo getServiceInfo();
+    }
 
-            component = resolveInfo.serviceInfo.getComponentName();
-            uid = resolveInfo.serviceInfo.applicationInfo.uid;
+    /**
+     * Information on the service selected as the best option for binding.
+     */
+    class BoundServiceInfo {
+
+        protected final @Nullable String mAction;
+        protected final int mUid;
+        protected final ComponentName mComponentName;
+
+        protected BoundServiceInfo(String action, ResolveInfo resolveInfo) {
+            this(action, resolveInfo.serviceInfo.applicationInfo.uid,
+                    resolveInfo.serviceInfo.getComponentName());
         }
 
-        private BoundService(int version, @Nullable ComponentName component,
-                boolean serviceIsMultiuser, @Nullable Bundle metadata, int uid) {
-            Preconditions.checkArgument(component != null || version == Integer.MIN_VALUE);
-            this.version = version;
-            this.component = component;
-            this.serviceIsMultiuser = serviceIsMultiuser;
-            this.metadata = metadata;
-            this.uid = uid;
+        protected BoundServiceInfo(String action, int uid, ComponentName componentName) {
+            mAction = action;
+            mUid = uid;
+            mComponentName = Objects.requireNonNull(componentName);
         }
 
-        public @Nullable String getPackageName() {
-            return component != null ? component.getPackageName() : null;
+        /** Returns the action associated with this bound service. */
+        public @Nullable String getAction() {
+            return mAction;
+        }
+
+        /** Returns the component of this bound service. */
+        public ComponentName getComponentName() {
+            return mComponentName;
+        }
+
+        /** Returns the user id for this bound service. */
+        public @UserIdInt int getUserId() {
+            return UserHandle.getUserId(mUid);
         }
 
         @Override
-        public boolean equals(Object o) {
+        public final boolean equals(Object o) {
             if (this == o) {
                 return true;
             }
-            if (!(o instanceof BoundService)) {
+            if (o == null || getClass() != o.getClass()) {
                 return false;
             }
-            BoundService that = (BoundService) o;
-            return version == that.version && uid == that.uid
-                    && Objects.equals(component, that.component);
+
+            BoundServiceInfo that = (BoundServiceInfo) o;
+            return mUid == that.mUid
+                    && Objects.equals(mAction, that.mAction)
+                    && mComponentName.equals(that.mComponentName);
         }
 
         @Override
-        public int hashCode() {
-            return Objects.hash(version, component, uid);
-        }
-
-        @Override
-        public int compareTo(BoundService that) {
-            // ServiceInfos with higher version numbers always win (having a version number >
-            // MIN_VALUE implies having a non-null component). if version numbers are equal, a
-            // non-null component wins over a null component. if the version numbers are equal and
-            // both components exist then we prefer components that work for all users vs components
-            // that only work for a single user at a time. otherwise everything's equal.
-            int ret = Integer.compare(version, that.version);
-            if (ret == 0) {
-                if (component == null && that.component != null) {
-                    ret = -1;
-                } else if (component != null && that.component == null) {
-                    ret = 1;
-                } else {
-                    if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM
-                            && UserHandle.getUserId(that.uid) == UserHandle.USER_SYSTEM) {
-                        ret = -1;
-                    } else if (UserHandle.getUserId(uid) == UserHandle.USER_SYSTEM
-                            && UserHandle.getUserId(that.uid) != UserHandle.USER_SYSTEM) {
-                        ret = 1;
-                    }
-                }
-            }
-            return ret;
+        public final int hashCode() {
+            return Objects.hash(mAction, mUid, mComponentName);
         }
 
         @Override
         public String toString() {
-            if (component == null) {
+            if (mComponentName == null) {
                 return "none";
             } else {
-                return component.toShortString() + "@" + version + "[u"
-                        + UserHandle.getUserId(uid) + "]";
+                return mUid + "/" + mComponentName.flattenToShortString();
             }
         }
     }
 
-    private final Context mContext;
-    private final Handler mHandler;
-    private final Intent mIntent;
-    private final Predicate<ResolveInfo> mServiceCheckPredicate;
-
-    private final PackageMonitor mPackageMonitor = new PackageMonitor() {
-        @Override
-        public boolean onPackageChanged(String packageName, int uid, String[] components) {
-            return true;
-        }
-
-        @Override
-        public void onSomePackagesChanged() {
-            onBestServiceChanged(false);
-        }
-    };
-    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action == null) {
-                return;
-            }
-            int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
-            if (userId == UserHandle.USER_NULL) {
-                return;
-            }
-
-            switch (action) {
-                case Intent.ACTION_USER_SWITCHED:
-                    onUserSwitched(userId);
-                    break;
-                case Intent.ACTION_USER_UNLOCKED:
-                    onUserUnlocked(userId);
-                    break;
-                default:
-                    break;
-            }
-
-        }
-    };
-
-    // read/write from handler thread only
-    private final Map<ComponentName, BoundService> mPendingBinds = new ArrayMap<>();
-
-    @Nullable
-    private final OnBindRunner mOnBind;
-
-    @Nullable
-    private final Runnable mOnUnbind;
-
-    // read/write from handler thread only
-    private boolean mRegistered;
-
-    // read/write from handler thread only
-    private int mCurrentUserId;
-
-    // write from handler thread only, read anywhere
-    private volatile BoundService mTargetService;
-    private volatile IBinder mBinder;
-
-    public ServiceWatcher(Context context, String action,
-            @Nullable OnBindRunner onBind, @Nullable Runnable onUnbind,
-            @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
-        this(context, FgThread.getHandler(), action, onBind, onUnbind, enableOverlayResId,
-                nonOverlayPackageResId, DEFAULT_SERVICE_CHECK_PREDICATE);
-    }
-
-    public ServiceWatcher(Context context, Handler handler, String action,
-            @Nullable OnBindRunner onBind, @Nullable Runnable onUnbind,
-            @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
-        this(context, handler, action, onBind, onUnbind, enableOverlayResId, nonOverlayPackageResId,
-                DEFAULT_SERVICE_CHECK_PREDICATE);
-    }
-
-    public ServiceWatcher(Context context, Handler handler, String action,
-            @Nullable OnBindRunner onBind, @Nullable Runnable onUnbind,
-            @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId,
-            @NonNull Predicate<ResolveInfo> serviceCheckPredicate) {
-        mContext = context;
-        mHandler = handler;
-        mIntent = new Intent(Objects.requireNonNull(action));
-        mServiceCheckPredicate = Objects.requireNonNull(serviceCheckPredicate);
-
-        Resources resources = context.getResources();
-        boolean enableOverlay = resources.getBoolean(enableOverlayResId);
-        if (!enableOverlay) {
-            mIntent.setPackage(resources.getString(nonOverlayPackageResId));
-        }
-
-        mOnBind = onBind;
-        mOnUnbind = onUnbind;
-
-        mCurrentUserId = UserHandle.USER_NULL;
-
-        mTargetService = BoundService.NONE;
-        mBinder = null;
-    }
-
-    /**
-     * Returns true if there is at least one component that could satisfy the ServiceWatcher's
-     * constraints.
-     */
-    public boolean checkServiceResolves() {
-        List<ResolveInfo> resolveInfos = mContext.getPackageManager()
-                .queryIntentServicesAsUser(mIntent,
-                        MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_SYSTEM_ONLY,
-                        UserHandle.USER_SYSTEM);
-        for (ResolveInfo resolveInfo : resolveInfos) {
-            if (mServiceCheckPredicate.test(resolveInfo)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Starts the process of determining the best matching service and maintaining a binding to it.
-     */
-    public void register() {
-        mHandler.sendMessage(PooledLambda.obtainMessage(ServiceWatcher::registerInternal,
-                ServiceWatcher.this));
-    }
-
-    private void registerInternal() {
-        Preconditions.checkState(!mRegistered);
-
-        mPackageMonitor.register(mContext, UserHandle.ALL, true, mHandler);
-
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
-        intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
-        mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, intentFilter, null,
-                mHandler);
-
-        // TODO: This makes the behavior of the class unpredictable as the caller needs
-        // to know the internal impl detail that calling register would pick the current user.
-        mCurrentUserId = ActivityManager.getCurrentUser();
-
-        mRegistered = true;
-
-        mHandler.post(() -> onBestServiceChanged(false));
-    }
-
-    /**
-     * Stops the process of determining the best matching service and releases any binding.
-     */
-    public void unregister() {
-        mHandler.sendMessage(PooledLambda.obtainMessage(ServiceWatcher::unregisterInternal,
-                ServiceWatcher.this));
-    }
-
-    private void unregisterInternal() {
-        Preconditions.checkState(mRegistered);
-
-        mRegistered = false;
-
-        mPackageMonitor.unregister();
-        mContext.unregisterReceiver(mBroadcastReceiver);
-
-        mHandler.post(() -> onBestServiceChanged(false));
-    }
-
-    private void onBestServiceChanged(boolean forceRebind) {
-        Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
-        BoundService bestServiceInfo = BoundService.NONE;
-
-        if (mRegistered) {
-            List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser(
-                    mIntent,
-                    GET_META_DATA | MATCH_DIRECT_BOOT_AUTO | MATCH_SYSTEM_ONLY,
-                    mCurrentUserId);
-            for (ResolveInfo resolveInfo : resolveInfos) {
-                if (!mServiceCheckPredicate.test(resolveInfo)) {
-                    continue;
-                }
-                BoundService serviceInfo = new BoundService(resolveInfo);
-                if (serviceInfo.compareTo(bestServiceInfo) > 0) {
-                    bestServiceInfo = serviceInfo;
-                }
-            }
-        }
-
-        if (forceRebind || !bestServiceInfo.equals(mTargetService)) {
-            rebind(bestServiceInfo);
-        }
-    }
-
-    private void rebind(BoundService newServiceInfo) {
-        Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
-        if (!mTargetService.equals(BoundService.NONE)) {
-            if (D) {
-                Log.d(TAG, "[" + mIntent.getAction() + "] unbinding from " + mTargetService);
-            }
-
-            mContext.unbindService(this);
-            onServiceDisconnected(mTargetService.component);
-            mPendingBinds.remove(mTargetService.component);
-            mTargetService = BoundService.NONE;
-        }
-
-        mTargetService = newServiceInfo;
-        if (mTargetService.equals(BoundService.NONE)) {
-            return;
-        }
-
-        Preconditions.checkState(mTargetService.component != null);
-
-        Log.i(TAG, getLogPrefix() + " binding to " + mTargetService);
-
-        Intent bindIntent = new Intent(mIntent).setComponent(mTargetService.component);
-        if (!mContext.bindServiceAsUser(bindIntent, this,
-                BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE,
-                mHandler, UserHandle.of(UserHandle.getUserId(mTargetService.uid)))) {
-            mTargetService = BoundService.NONE;
-            Log.e(TAG, getLogPrefix() + " unexpected bind failure - retrying later");
-            mHandler.postDelayed(() -> onBestServiceChanged(false), RETRY_DELAY_MS);
-        } else {
-            mPendingBinds.put(mTargetService.component, mTargetService);
-        }
-    }
-
-    @Override
-    public final void onServiceConnected(ComponentName component, IBinder binder) {
-        Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-        Preconditions.checkState(mBinder == null);
-
-        if (D) {
-            Log.d(TAG, getLogPrefix() + " connected to " + component.toShortString());
-        }
-
-        final BoundService boundService = mPendingBinds.remove(component);
-        if (boundService == null) {
-            return;
-        }
-
-        mBinder = binder;
-        if (mOnBind != null) {
-            try {
-                mOnBind.run(binder, boundService);
-            } catch (RuntimeException | RemoteException e) {
-                // binders may propagate some specific non-RemoteExceptions from the other side
-                // through the binder as well - we cannot allow those to crash the system server
-                Log.e(TAG, getLogPrefix() + " exception running on " + component, e);
-            }
-        }
-    }
-
-    @Override
-    public final void onServiceDisconnected(ComponentName component) {
-        Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
-        if (mBinder == null) {
-            return;
-        }
-
-        if (D) {
-            Log.d(TAG, getLogPrefix() + " disconnected from " + component.toShortString());
-        }
-
-        mBinder = null;
-        if (mOnUnbind != null) {
-            mOnUnbind.run();
-        }
-    }
-
-    @Override
-    public final void onBindingDied(ComponentName component) {
-        Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
-        Log.i(TAG, getLogPrefix() + " " + component.toShortString() + " died");
-
-        onBestServiceChanged(true);
-    }
-
-    @Override
-    public final void onNullBinding(ComponentName component) {
-        Log.e(TAG, getLogPrefix() + " " + component.toShortString() + " has null binding");
-    }
-
-    void onUserSwitched(@UserIdInt int userId) {
-        mCurrentUserId = userId;
-        onBestServiceChanged(false);
-    }
-
-    void onUserUnlocked(@UserIdInt int userId) {
-        if (userId == mCurrentUserId) {
-            onBestServiceChanged(false);
-        }
-    }
-
     /**
-     * Runs the given function asynchronously if and only if currently connected. Suppresses any
-     * RemoteException thrown during execution.
+     * Creates a new ServiceWatcher instance.
      */
-    public final void runOnBinder(BinderRunner runner) {
-        mHandler.post(() -> {
-            if (mBinder == null) {
-                runner.onError();
-                return;
-            }
-
-            try {
-                runner.run(mBinder);
-            } catch (RuntimeException | RemoteException e) {
-                // binders may propagate some specific non-RemoteExceptions from the other side
-                // through the binder as well - we cannot allow those to crash the system server
-                Log.e(TAG, getLogPrefix() + " exception running on " + mTargetService, e);
-                runner.onError();
-            }
-        });
-    }
-
-    private String getLogPrefix() {
-        return "[" + mIntent.getAction() + "]";
-    }
-
-    @Override
-    public String toString() {
-        return mTargetService.toString();
+    static <TBoundServiceInfo extends BoundServiceInfo> ServiceWatcher create(
+            Context context,
+            String tag,
+            ServiceSupplier<TBoundServiceInfo> serviceSupplier,
+            @Nullable ServiceListener<? super TBoundServiceInfo> serviceListener) {
+        return create(context, FgThread.getHandler(), tag, serviceSupplier, serviceListener);
     }
 
     /**
-     * Dump for debugging.
+     * Creates a new ServiceWatcher instance that runs on the given handler.
      */
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("target service=" + mTargetService);
-        pw.println("connected=" + (mBinder != null));
+    static <TBoundServiceInfo extends BoundServiceInfo> ServiceWatcher create(
+            Context context,
+            Handler handler,
+            String tag,
+            ServiceSupplier<TBoundServiceInfo> serviceSupplier,
+            @Nullable ServiceListener<? super TBoundServiceInfo> serviceListener) {
+        return new ServiceWatcherImpl<>(context, handler, tag, serviceSupplier, serviceListener);
     }
-}
+
+    /**
+     * Returns true if there is at least one service that the ServiceWatcher could hypothetically
+     * bind to, as selected by the {@link ServiceSupplier}.
+     */
+    boolean checkServiceResolves();
+
+    /**
+     * Registers the ServiceWatcher, so that it will begin maintaining an active binding to the
+     * service selected by {@link ServiceSupplier}, until {@link #unregister()} is called.
+     */
+    void register();
+
+    /**
+     * Unregisters the ServiceWatcher, so that it will release any active bindings. If the
+     * ServiceWatcher is currently bound, this will result in one final
+     * {@link ServiceListener#onUnbind()} invocation, which may happen after this method completes
+     * (but which is guaranteed to occur before any further
+     * {@link ServiceListener#onBind(IBinder, BoundServiceInfo)} invocation in response to a later
+     * call to {@link #register()}).
+     */
+    void unregister();
+
+    /**
+     * Runs the given binder operation on the currently bound service (if available). The operation
+     * will always fail if the ServiceWatcher is not currently registered.
+     */
+    void runOnBinder(BinderOperation operation);
+
+    /**
+     * Dumps ServiceWatcher information.
+     */
+    void dump(PrintWriter pw);
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
new file mode 100644
index 0000000..7757a7a
--- /dev/null
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
@@ -0,0 +1,297 @@
+/*
+ * 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.servicewatcher;
+
+import static android.content.Context.BIND_AUTO_CREATE;
+import static android.content.Context.BIND_NOT_FOREGROUND;
+import static android.content.Context.BIND_NOT_VISIBLE;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.util.Preconditions;
+import com.android.server.servicewatcher.ServiceWatcher.BoundServiceInfo;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceChangedListener;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * Implementation of ServiceWatcher. Keeping the implementation separate from the interface allows
+ * us to store the generic relationship between the service supplier and the service listener, while
+ * hiding the generics from clients, simplifying the API.
+ */
+class ServiceWatcherImpl<TBoundServiceInfo extends BoundServiceInfo> implements ServiceWatcher,
+        ServiceChangedListener {
+
+    static final String TAG = "ServiceWatcher";
+    static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
+
+    static final long RETRY_DELAY_MS = 15 * 1000;
+
+    final Context mContext;
+    final Handler mHandler;
+    final String mTag;
+    final ServiceSupplier<TBoundServiceInfo> mServiceSupplier;
+    final @Nullable ServiceListener<? super TBoundServiceInfo> mServiceListener;
+
+    private final PackageMonitor mPackageMonitor = new PackageMonitor() {
+        @Override
+        public boolean onPackageChanged(String packageName, int uid, String[] components) {
+            return true;
+        }
+
+        @Override
+        public void onSomePackagesChanged() {
+            onServiceChanged(false);
+        }
+    };
+
+    @GuardedBy("this")
+    private boolean mRegistered = false;
+    @GuardedBy("this")
+    private MyServiceConnection mServiceConnection = new MyServiceConnection(null);
+
+    ServiceWatcherImpl(Context context, Handler handler, String tag,
+            ServiceSupplier<TBoundServiceInfo> serviceSupplier,
+            ServiceListener<? super TBoundServiceInfo> serviceListener) {
+        mContext = context;
+        mHandler = handler;
+        mTag = tag;
+        mServiceSupplier = serviceSupplier;
+        mServiceListener = serviceListener;
+    }
+
+    @Override
+    public boolean checkServiceResolves() {
+        return mServiceSupplier.hasMatchingService();
+    }
+
+    @Override
+    public synchronized void register() {
+        Preconditions.checkState(!mRegistered);
+
+        mRegistered = true;
+        mPackageMonitor.register(mContext, UserHandle.ALL, /*externalStorage=*/ true, mHandler);
+        mServiceSupplier.register(this);
+
+        onServiceChanged(false);
+    }
+
+    @Override
+    public synchronized void unregister() {
+        Preconditions.checkState(mRegistered);
+
+        mServiceSupplier.unregister();
+        mPackageMonitor.unregister();
+        mRegistered = false;
+
+        onServiceChanged(false);
+    }
+
+    @Override
+    public synchronized void onServiceChanged() {
+        onServiceChanged(false);
+    }
+
+    @Override
+    public synchronized void runOnBinder(BinderOperation operation) {
+        MyServiceConnection serviceConnection = mServiceConnection;
+        mHandler.post(() -> serviceConnection.runOnBinder(operation));
+    }
+
+    synchronized void onServiceChanged(boolean forceRebind) {
+        TBoundServiceInfo newBoundServiceInfo;
+        if (mRegistered) {
+            newBoundServiceInfo = mServiceSupplier.getServiceInfo();
+        } else {
+            newBoundServiceInfo = null;
+        }
+
+        if (forceRebind || !Objects.equals(mServiceConnection.getBoundServiceInfo(),
+                newBoundServiceInfo)) {
+            MyServiceConnection oldServiceConnection = mServiceConnection;
+            MyServiceConnection newServiceConnection = new MyServiceConnection(newBoundServiceInfo);
+            mServiceConnection = newServiceConnection;
+            mHandler.post(() -> {
+                oldServiceConnection.unbind();
+                newServiceConnection.bind();
+            });
+        }
+    }
+
+    @Override
+    public String toString() {
+        return mServiceConnection.getBoundServiceInfo().toString();
+    }
+
+    @Override
+    public void dump(PrintWriter pw) {
+        pw.println("target service=" + mServiceConnection.getBoundServiceInfo());
+        pw.println("connected=" + mServiceConnection.isConnected());
+    }
+
+    // runs on the handler thread, and expects most of it's methods to be called from that thread
+    private class MyServiceConnection implements ServiceConnection {
+
+        private final @Nullable TBoundServiceInfo mBoundServiceInfo;
+
+        // volatile so that isConnected can be called from any thread easily
+        private volatile @Nullable IBinder mBinder;
+        private @Nullable Runnable mRebinder;
+
+        MyServiceConnection(@Nullable TBoundServiceInfo boundServiceInfo) {
+            mBoundServiceInfo = boundServiceInfo;
+        }
+
+        // may be called from any thread
+        @Nullable TBoundServiceInfo getBoundServiceInfo() {
+            return mBoundServiceInfo;
+        }
+
+        // may be called from any thread
+        boolean isConnected() {
+            return mBinder != null;
+        }
+
+        void bind() {
+            Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+            if (mBoundServiceInfo == null) {
+                return;
+            }
+
+            Log.i(TAG, "[" + mTag + "] binding to " + mBoundServiceInfo);
+
+            Intent bindIntent = new Intent(mBoundServiceInfo.getAction()).setComponent(
+                    mBoundServiceInfo.getComponentName());
+            if (!mContext.bindServiceAsUser(bindIntent, this,
+                    BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE,
+                    mHandler, UserHandle.of(mBoundServiceInfo.getUserId()))) {
+                Log.e(TAG, "[" + mTag + "] unexpected bind failure - retrying later");
+                mRebinder = this::bind;
+                mHandler.postDelayed(mRebinder, RETRY_DELAY_MS);
+            } else {
+                mRebinder = null;
+            }
+        }
+
+        void unbind() {
+            Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+            if (mBoundServiceInfo == null) {
+                return;
+            }
+
+            if (D) {
+                Log.d(TAG, "[" + mTag + "] unbinding from " + mBoundServiceInfo);
+            }
+
+            if (mRebinder != null) {
+                mHandler.removeCallbacks(mRebinder);
+                mRebinder = null;
+            } else {
+                mContext.unbindService(this);
+            }
+
+            onServiceDisconnected(mBoundServiceInfo.getComponentName());
+        }
+
+        void runOnBinder(BinderOperation operation) {
+            Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+            if (mBinder == null) {
+                operation.onError();
+                return;
+            }
+
+            try {
+                operation.run(mBinder);
+            } catch (RuntimeException | RemoteException e) {
+                // binders may propagate some specific non-RemoteExceptions from the other side
+                // through the binder as well - we cannot allow those to crash the system server
+                Log.e(TAG, "[" + mTag + "] error running operation on " + mBoundServiceInfo, e);
+                operation.onError();
+            }
+        }
+
+        @Override
+        public final void onServiceConnected(ComponentName component, IBinder binder) {
+            Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+            Preconditions.checkState(mBinder == null);
+
+            if (D) {
+                Log.d(TAG, "[" + mTag + "] connected to " + component.toShortString());
+            }
+
+            mBinder = binder;
+
+            if (mServiceListener != null) {
+                try {
+                    mServiceListener.onBind(binder, mBoundServiceInfo);
+                } catch (RuntimeException | RemoteException e) {
+                    // binders may propagate some specific non-RemoteExceptions from the other side
+                    // through the binder as well - we cannot allow those to crash the system server
+                    Log.e(TAG, "[" + mTag + "] error running operation on " + mBoundServiceInfo, e);
+                }
+            }
+        }
+
+        @Override
+        public final void onServiceDisconnected(ComponentName component) {
+            Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+            if (mBinder == null) {
+                return;
+            }
+
+            if (D) {
+                Log.d(TAG, "[" + mTag + "] disconnected from " + mBoundServiceInfo);
+            }
+
+            mBinder = null;
+            if (mServiceListener != null) {
+                mServiceListener.onUnbind();
+            }
+        }
+
+        @Override
+        public final void onBindingDied(ComponentName component) {
+            Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+            Log.i(TAG, "[" + mTag + "] " + mBoundServiceInfo + " died");
+
+            onServiceChanged(true);
+        }
+
+        @Override
+        public final void onNullBinding(ComponentName component) {
+            Log.e(TAG, "[" + mTag + "] " + mBoundServiceInfo + " has null binding");
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java
index dedb3ac..ff6c2f5 100644
--- a/services/core/java/com/android/server/storage/StorageSessionController.java
+++ b/services/core/java/com/android/server/storage/StorageSessionController.java
@@ -27,11 +27,13 @@
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
 import android.os.IVold;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.storage.StorageManager;
 import android.os.storage.StorageVolume;
 import android.os.storage.VolumeInfo;
@@ -53,6 +55,7 @@
 
     private final Object mLock = new Object();
     private final Context mContext;
+    private final UserManager mUserManager;
     @GuardedBy("mLock")
     private final SparseArray<StorageUserConnection> mConnections = new SparseArray<>();
 
@@ -63,6 +66,30 @@
 
     public StorageSessionController(Context context) {
         mContext = Objects.requireNonNull(context);
+        mUserManager = mContext.getSystemService(UserManager.class);
+    }
+
+    /**
+     * Returns userId for the volume to be used in the StorageUserConnection.
+     * If the user is a clone profile, it will use the same connection
+     * as the parent user, and hence this method returns the parent's userId. Else, it returns the
+     * volume's mountUserId
+     * @param vol for which the storage session has to be started
+     * @return userId for connection for this volume
+     */
+    public int getConnectionUserIdForVolume(VolumeInfo vol) {
+        final Context volumeUserContext = mContext.createContextAsUser(
+                UserHandle.of(vol.mountUserId), 0);
+        boolean sharesMediaWithParent = volumeUserContext.getSystemService(
+                UserManager.class).sharesMediaWithParent();
+
+        UserInfo userInfo = mUserManager.getUserInfo(vol.mountUserId);
+        if (userInfo != null && sharesMediaWithParent) {
+            // Clones use the same connection as their parent
+            return userInfo.profileGroupId;
+        } else {
+            return vol.mountUserId;
+        }
     }
 
     /**
@@ -88,7 +115,7 @@
         Slog.i(TAG, "On volume mount " + vol);
 
         String sessionId = vol.getId();
-        int userId = vol.getMountUserId();
+        int userId = getConnectionUserIdForVolume(vol);
 
         StorageUserConnection connection = null;
         synchronized (mLock) {
@@ -120,7 +147,7 @@
             return;
         }
         String sessionId = vol.getId();
-        int userId = vol.getMountUserId();
+        int userId = getConnectionUserIdForVolume(vol);
 
         StorageUserConnection connection = null;
         synchronized (mLock) {
@@ -191,7 +218,7 @@
 
         Slog.i(TAG, "On volume remove " + vol);
         String sessionId = vol.getId();
-        int userId = vol.getMountUserId();
+        int userId = getConnectionUserIdForVolume(vol);
 
         synchronized (mLock) {
             StorageUserConnection connection = mConnections.get(userId);
diff --git a/services/core/java/com/android/server/timezone/TimeZoneUpdateIdler.java b/services/core/java/com/android/server/timezone/TimeZoneUpdateIdler.java
index a7767a4..23e3eba 100644
--- a/services/core/java/com/android/server/timezone/TimeZoneUpdateIdler.java
+++ b/services/core/java/com/android/server/timezone/TimeZoneUpdateIdler.java
@@ -16,8 +16,6 @@
 
 package com.android.server.timezone;
 
-import com.android.server.LocalServices;
-
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
 import android.app.job.JobScheduler;
@@ -26,6 +24,8 @@
 import android.content.Context;
 import android.util.Slog;
 
+import com.android.server.LocalServices;
+
 /**
  * A JobService used to trigger time zone rules update work when a device falls idle.
  */
@@ -55,7 +55,7 @@
     @Override
     public boolean onStopJob(JobParameters params) {
         // Reschedule if stopped unless it was cancelled due to unschedule().
-        boolean reschedule = params.getStopReason() != JobParameters.REASON_CANCELED;
+        boolean reschedule = params.getStopReason() != JobParameters.STOP_REASON_CANCELLED_BY_APP;
         Slog.d(TAG, "onStopJob() called: Reschedule=" + reschedule);
         return reschedule;
     }
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
index 2452c8d..222e852 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
@@ -18,8 +18,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
-import android.net.ConnectivityManager;
 import android.os.SystemProperties;
 import android.util.ArraySet;
 
@@ -124,9 +124,7 @@
      * device.
      */
     public boolean isTelephonyTimeZoneDetectionFeatureSupported() {
-        // TODO b/150583524 Avoid the use of a deprecated API.
-        return mContext.getSystemService(ConnectivityManager.class)
-                .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
     }
 
     /**
diff --git a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
index 4fa920e..f054c57 100644
--- a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
@@ -47,7 +47,7 @@
             @NonNull String providerName,
             @NonNull LocationTimeZoneProviderProxy proxy) {
         super(providerMetricsLogger, threadingDomain, providerName,
-                new ZoneInfoDbTimeZoneIdValidator());
+                new ZoneInfoDbTimeZoneProviderEventPreProcessor());
         mProxy = Objects.requireNonNull(proxy);
     }
 
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
index cc815dc6..e116a87 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
@@ -20,7 +20,6 @@
 import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY;
 
 import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.debugLog;
-import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.infoLog;
 import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog;
 import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
 import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
@@ -86,18 +85,6 @@
     }
 
     /**
-     * Used by {@link LocationTimeZoneProvider} to check if time zone IDs are understood
-     * by the platform.
-     */
-    interface TimeZoneIdValidator {
-
-        /**
-         * Returns whether {@code timeZoneId} is supported by the platform or not.
-         */
-        boolean isValid(@NonNull String timeZoneId);
-    }
-
-    /**
      * Listener interface used to log provider events for metrics.
      */
     interface ProviderMetricsLogger {
@@ -386,19 +373,20 @@
     // Non-null and effectively final after initialize() is called.
     ProviderListener mProviderListener;
 
-    @NonNull private TimeZoneIdValidator mTimeZoneIdValidator;
+    @NonNull private final TimeZoneProviderEventPreProcessor mTimeZoneProviderEventPreProcessor;
 
     /** Creates the instance. */
     LocationTimeZoneProvider(@NonNull ProviderMetricsLogger providerMetricsLogger,
             @NonNull ThreadingDomain threadingDomain,
             @NonNull String providerName,
-            @NonNull TimeZoneIdValidator timeZoneIdValidator) {
+            @NonNull TimeZoneProviderEventPreProcessor timeZoneProviderEventPreProcessor) {
         mThreadingDomain = Objects.requireNonNull(threadingDomain);
         mProviderMetricsLogger = Objects.requireNonNull(providerMetricsLogger);
         mInitializationTimeoutQueue = threadingDomain.createSingleRunnableQueue();
         mSharedLock = threadingDomain.getLockObject();
         mProviderName = Objects.requireNonNull(providerName);
-        mTimeZoneIdValidator = Objects.requireNonNull(timeZoneIdValidator);
+        mTimeZoneProviderEventPreProcessor =
+                Objects.requireNonNull(timeZoneProviderEventPreProcessor);
     }
 
     /**
@@ -639,24 +627,8 @@
         mThreadingDomain.assertCurrentThread();
         Objects.requireNonNull(timeZoneProviderEvent);
 
-        // If the provider has made a suggestion with unknown time zone IDs it cannot be used to set
-        // the device's time zone. This logic prevents bad time zone IDs entering the time zone
-        // detection logic from third party code.
-        //
-        // An event containing an unknown time zone ID could occur if the provider is using a
-        // different TZDB version than the device. Provider developers are expected to take steps to
-        // avoid version skew problem, e.g. by ensuring atomic updates with the platform time zone
-        // rules, or providing IDs based on the device's TZDB version, so this is not considered a
-        // common case.
-        //
-        // Treating a suggestion containing unknown time zone IDs as "uncertain" in the primary
-        // enables immediate failover to a secondary provider, one that might provide valid IDs for
-        // the same location, which should provide better behavior than just ignoring the event.
-        if (hasInvalidTimeZones(timeZoneProviderEvent)) {
-            infoLog("event=" + timeZoneProviderEvent + " has unsupported time zones. "
-                    + "Replacing it with uncertain event.");
-            timeZoneProviderEvent = TimeZoneProviderEvent.createUncertainEvent();
-        }
+        timeZoneProviderEvent =
+                mTimeZoneProviderEventPreProcessor.preProcess(timeZoneProviderEvent);
 
         synchronized (mSharedLock) {
             debugLog("handleTimeZoneProviderEvent: mProviderName=" + mProviderName
@@ -755,20 +727,6 @@
         }
     }
 
-    private boolean hasInvalidTimeZones(@NonNull TimeZoneProviderEvent event) {
-        if (event.getSuggestion() == null) {
-            return false;
-        }
-
-        for (String timeZone : event.getSuggestion().getTimeZoneIds()) {
-            if (!mTimeZoneIdValidator.isValid(timeZone)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
     @GuardedBy("mSharedLock")
     private void assertIsStarted() {
         ProviderState currentState = mCurrentState.get();
diff --git a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java
index 0b51488..6c3f016 100644
--- a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java
+++ b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java
@@ -16,19 +16,14 @@
 
 package com.android.server.timezonedetector.location;
 
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.Manifest.permission.BIND_TIME_ZONE_PROVIDER_SERVICE;
+import static android.Manifest.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE;
 import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY;
 import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY;
 
-import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog;
-
-import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -39,11 +34,12 @@
 import android.util.IndentingPrintWriter;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier.BoundServiceInfo;
 import com.android.server.servicewatcher.ServiceWatcher;
-import com.android.server.servicewatcher.ServiceWatcher.BoundService;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceListener;
 
 import java.util.Objects;
-import java.util.function.Predicate;
 
 /**
  * System server-side proxy for ITimeZoneProvider implementations, i.e. this provides the
@@ -52,7 +48,8 @@
  * different process. As "remote" providers are bound / unbound this proxy will rebind to the "best"
  * available remote process.
  */
-class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy {
+class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy implements
+        ServiceListener<BoundServiceInfo> {
 
     @NonNull private final ServiceWatcher mServiceWatcher;
 
@@ -69,38 +66,13 @@
         super(context, threadingDomain);
         mManagerProxy = null;
         mRequest = TimeZoneProviderRequest.createStopUpdatesRequest();
-
-        // A predicate that is used to confirm that an intent service can be used as a
-        // location-based TimeZoneProvider. The service must:
-        // 1) Declare android:permission="android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE" - this
-        //    ensures that the provider will only communicate with the system server.
-        // 2) Be in an application that has been granted the
-        //    android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE permission. This
-        //    ensures only trusted time zone providers will be discovered.
-        final String requiredClientPermission = Manifest.permission.BIND_TIME_ZONE_PROVIDER_SERVICE;
-        final String requiredPermission =
-                Manifest.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE;
-        Predicate<ResolveInfo> intentServiceCheckPredicate = resolveInfo -> {
-            ServiceInfo serviceInfo = resolveInfo.serviceInfo;
-
-            boolean hasClientPermissionRequirement =
-                    requiredClientPermission.equals(serviceInfo.permission);
-
-            String packageName = serviceInfo.packageName;
-            PackageManager packageManager = context.getPackageManager();
-            int checkResult = packageManager.checkPermission(requiredPermission, packageName);
-            boolean hasRequiredPermission = checkResult == PERMISSION_GRANTED;
-
-            boolean result = hasClientPermissionRequirement && hasRequiredPermission;
-            if (!result) {
-                warnLog("resolveInfo=" + resolveInfo + " does not meet requirements:"
-                        + " hasClientPermissionRequirement=" + hasClientPermissionRequirement
-                        + ", hasRequiredPermission=" + hasRequiredPermission);
-            }
-            return result;
-        };
-        mServiceWatcher = new ServiceWatcher(context, handler, action, this::onBind, this::onUnbind,
-                enableOverlayResId, nonOverlayPackageResId, intentServiceCheckPredicate);
+        mServiceWatcher = ServiceWatcher.create(context,
+                handler,
+                "RealLocationTimeZoneProviderProxy",
+                new CurrentUserServiceSupplier(context, action, enableOverlayResId,
+                        nonOverlayPackageResId, BIND_TIME_ZONE_PROVIDER_SERVICE,
+                        INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE),
+                this);
     }
 
     @Override
@@ -123,7 +95,8 @@
         return resolves;
     }
 
-    private void onBind(IBinder binder, BoundService boundService) {
+    @Override
+    public void onBind(IBinder binder, BoundServiceInfo boundService) {
         mThreadingDomain.assertCurrentThread();
 
         synchronized (mSharedLock) {
@@ -138,7 +111,8 @@
         }
     }
 
-    private void onUnbind() {
+    @Override
+    public void onUnbind() {
         mThreadingDomain.assertCurrentThread();
 
         synchronized (mSharedLock) {
@@ -199,7 +173,7 @@
         synchronized (mSharedLock) {
             ipw.println("{RealLocationTimeZoneProviderProxy}");
             ipw.println("mRequest=" + mRequest);
-            mServiceWatcher.dump(null, ipw, args);
+            mServiceWatcher.dump(ipw);
         }
     }
 
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEventPreProcessor.java
similarity index 64%
rename from services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
rename to services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEventPreProcessor.java
index cab5ad2..951e9d0 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
+++ b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEventPreProcessor.java
@@ -18,13 +18,16 @@
 
 import android.annotation.NonNull;
 
-import com.android.i18n.timezone.ZoneInfoDb;
+/**
+ * Used by {@link LocationTimeZoneProvider} to ensure that all time zone IDs are understood by the
+ * platform.
+ */
+public interface TimeZoneProviderEventPreProcessor {
 
-class ZoneInfoDbTimeZoneIdValidator implements
-        LocationTimeZoneProvider.TimeZoneIdValidator {
+    /**
+     * May return uncertain event if {@code timeZoneProviderEvent} is ill-formed or drop/rewrite
+     * time zone IDs.
+     */
+    TimeZoneProviderEvent preProcess(@NonNull TimeZoneProviderEvent timeZoneProviderEvent);
 
-    @Override
-    public boolean isValid(@NonNull String timeZoneId) {
-        return ZoneInfoDb.getInstance().hasTimeZone(timeZoneId);
-    }
 }
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
new file mode 100644
index 0000000..0f4367d
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.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.server.timezonedetector.location;
+
+import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.infoLog;
+
+import android.annotation.NonNull;
+
+import com.android.i18n.timezone.ZoneInfoDb;
+
+/**
+ * {@link TimeZoneProviderEventPreProcessor} implementation which makes validations against
+ * {@link ZoneInfoDb}.
+ */
+public class ZoneInfoDbTimeZoneProviderEventPreProcessor
+        implements TimeZoneProviderEventPreProcessor {
+
+    /**
+     * Returns uncertain event if {@code event} has at least one unsupported time zone ID.
+     */
+    @Override
+    public TimeZoneProviderEvent preProcess(@NonNull TimeZoneProviderEvent event) {
+        if (event.getSuggestion() == null || event.getSuggestion().getTimeZoneIds().isEmpty()) {
+            return event;
+        }
+
+        // If the provider has made a suggestion with unknown time zone IDs it cannot be used to set
+        // the device's time zone. This logic prevents bad time zone IDs entering the time zone
+        // detection logic from third party code.
+        //
+        // An event containing an unknown time zone ID could occur if the provider is using a
+        // different TZDB version than the device. Provider developers are expected to take steps to
+        // avoid version skew problem, e.g. by ensuring atomic updates with the platform time zone
+        // rules, or providing IDs based on the device's TZDB version, so this is not considered a
+        // common case.
+        //
+        // Treating a suggestion containing unknown time zone IDs as "uncertain" in the primary
+        // enables immediate failover to a secondary provider, one that might provide valid IDs for
+        // the same location, which should provide better behavior than just ignoring the event.
+        if (hasInvalidZones(event)) {
+            return TimeZoneProviderEvent.createUncertainEvent();
+        }
+
+        return event;
+    }
+
+    private static boolean hasInvalidZones(TimeZoneProviderEvent event) {
+        for (String timeZone : event.getSuggestion().getTimeZoneIds()) {
+            if (!ZoneInfoDb.getInstance().hasTimeZone(timeZone)) {
+                infoLog("event=" + event + " has unsupported zone(" + timeZone + ")");
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index e84ee672..1e897ea 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -16,17 +16,23 @@
 
 package com.android.server.vibrator;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.CombinedVibrationEffect;
 import android.os.IBinder;
 import android.os.SystemClock;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 
 import java.text.SimpleDateFormat;
 import java.util.Date;
+import java.util.List;
+import java.util.function.Function;
 
 /** Represents a vibration request to the vibrator service. */
 final class Vibration {
@@ -61,6 +67,7 @@
     public final String opPkg;
     public final String reason;
     public final IBinder token;
+    public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>();
 
     /** The actual effect to be played. */
     @Nullable
@@ -113,17 +120,70 @@
     }
 
     /**
-     * Replace this vibration effect if given {@code scaledEffect} is different, preserving the
-     * original one for debug purposes.
+     * Return the effect to be played when given prebaked effect id is not supported by the
+     * vibrator.
      */
-    public void updateEffect(@NonNull CombinedVibrationEffect newEffect) {
-        if (newEffect.equals(mEffect)) {
-            return;
+    @Nullable
+    public VibrationEffect getFallback(int effectId) {
+        return mFallbacks.get(effectId);
+    }
+
+    /**
+     * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported,
+     * which might be necessary for replacement in realtime.
+     */
+    public void addFallback(int effectId, VibrationEffect effect) {
+        mFallbacks.put(effectId, effect);
+    }
+
+    /**
+     * Applied update function to the current effect held by this vibration, and to each fallback
+     * effect added.
+     */
+    public void updateEffects(Function<VibrationEffect, VibrationEffect> updateFn) {
+        CombinedVibrationEffect newEffect = transformCombinedEffect(mEffect, updateFn);
+        if (!newEffect.equals(mEffect)) {
+            if (mOriginalEffect == null) {
+                mOriginalEffect = mEffect;
+            }
+            mEffect = newEffect;
         }
-        if (mOriginalEffect == null) {
-            mOriginalEffect = mEffect;
+        for (int i = 0; i < mFallbacks.size(); i++) {
+            mFallbacks.setValueAt(i, updateFn.apply(mFallbacks.valueAt(i)));
         }
-        mEffect = newEffect;
+    }
+
+    /**
+     * Creates a new {@link CombinedVibrationEffect} by applying the given transformation function
+     * to each {@link VibrationEffect}.
+     */
+    private static CombinedVibrationEffect transformCombinedEffect(
+            CombinedVibrationEffect combinedEffect, Function<VibrationEffect, VibrationEffect> fn) {
+        if (combinedEffect instanceof CombinedVibrationEffect.Mono) {
+            VibrationEffect effect = ((CombinedVibrationEffect.Mono) combinedEffect).getEffect();
+            return CombinedVibrationEffect.createSynced(fn.apply(effect));
+        } else if (combinedEffect instanceof CombinedVibrationEffect.Stereo) {
+            SparseArray<VibrationEffect> effects =
+                    ((CombinedVibrationEffect.Stereo) combinedEffect).getEffects();
+            CombinedVibrationEffect.SyncedCombination combination =
+                    CombinedVibrationEffect.startSynced();
+            for (int i = 0; i < effects.size(); i++) {
+                combination.addVibrator(effects.keyAt(i), fn.apply(effects.valueAt(i)));
+            }
+            return combination.combine();
+        } else if (combinedEffect instanceof CombinedVibrationEffect.Sequential) {
+            List<CombinedVibrationEffect> effects =
+                    ((CombinedVibrationEffect.Sequential) combinedEffect).getEffects();
+            CombinedVibrationEffect.SequentialCombination combination =
+                    CombinedVibrationEffect.startSequential();
+            for (CombinedVibrationEffect effect : effects) {
+                combination.addNext(transformCombinedEffect(effect, fn));
+            }
+            return combination.combine();
+        } else {
+            // Unknown combination, return same effect.
+            return combinedEffect;
+        }
     }
 
     /** Return true is current status is different from {@link Status#RUNNING}. */
@@ -272,57 +332,49 @@
         private void dumpEffect(
                 ProtoOutputStream proto, long fieldId, VibrationEffect effect) {
             final long token = proto.start(fieldId);
-            if (effect instanceof VibrationEffect.OneShot) {
-                dumpEffect(proto, VibrationEffectProto.ONESHOT, (VibrationEffect.OneShot) effect);
-            } else if (effect instanceof VibrationEffect.Waveform) {
-                dumpEffect(proto, VibrationEffectProto.WAVEFORM, (VibrationEffect.Waveform) effect);
-            } else if (effect instanceof VibrationEffect.Prebaked) {
-                dumpEffect(proto, VibrationEffectProto.PREBAKED, (VibrationEffect.Prebaked) effect);
-            } else if (effect instanceof VibrationEffect.Composed) {
-                dumpEffect(proto, VibrationEffectProto.COMPOSED, (VibrationEffect.Composed) effect);
+            VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+            for (VibrationEffectSegment segment : composed.getSegments()) {
+                dumpEffect(proto, VibrationEffectProto.SEGMENTS, segment);
             }
+            proto.write(VibrationEffectProto.REPEAT, composed.getRepeatIndex());
             proto.end(token);
         }
 
         private void dumpEffect(ProtoOutputStream proto, long fieldId,
-                VibrationEffect.OneShot effect) {
+                VibrationEffectSegment segment) {
             final long token = proto.start(fieldId);
-            proto.write(OneShotProto.DURATION, (int) effect.getDuration());
-            proto.write(OneShotProto.AMPLITUDE, effect.getAmplitude());
+            if (segment instanceof StepSegment) {
+                dumpEffect(proto, SegmentProto.STEP, (StepSegment) segment);
+            } else if (segment instanceof PrebakedSegment) {
+                dumpEffect(proto, SegmentProto.PREBAKED, (PrebakedSegment) segment);
+            } else if (segment instanceof PrimitiveSegment) {
+                dumpEffect(proto, SegmentProto.PRIMITIVE, (PrimitiveSegment) segment);
+            }
+            proto.end(token);
+        }
+
+        private void dumpEffect(ProtoOutputStream proto, long fieldId, StepSegment segment) {
+            final long token = proto.start(fieldId);
+            proto.write(StepSegmentProto.DURATION, segment.getDuration());
+            proto.write(StepSegmentProto.AMPLITUDE, segment.getAmplitude());
             proto.end(token);
         }
 
         private void dumpEffect(ProtoOutputStream proto, long fieldId,
-                VibrationEffect.Waveform effect) {
+                PrebakedSegment segment) {
             final long token = proto.start(fieldId);
-            for (long timing : effect.getTimings()) {
-                proto.write(WaveformProto.TIMINGS, (int) timing);
-            }
-            for (int amplitude : effect.getAmplitudes()) {
-                proto.write(WaveformProto.AMPLITUDES, amplitude);
-            }
-            proto.write(WaveformProto.REPEAT, effect.getRepeatIndex() >= 0);
+            proto.write(PrebakedSegmentProto.EFFECT_ID, segment.getEffectId());
+            proto.write(PrebakedSegmentProto.EFFECT_STRENGTH, segment.getEffectStrength());
+            proto.write(PrebakedSegmentProto.FALLBACK, segment.shouldFallback());
             proto.end(token);
         }
 
         private void dumpEffect(ProtoOutputStream proto, long fieldId,
-                VibrationEffect.Prebaked effect) {
+                PrimitiveSegment segment) {
             final long token = proto.start(fieldId);
-            proto.write(PrebakedProto.EFFECT_ID, effect.getId());
-            proto.write(PrebakedProto.EFFECT_STRENGTH, effect.getEffectStrength());
-            proto.write(PrebakedProto.FALLBACK, effect.shouldFallback());
-            proto.end(token);
-        }
-
-        private void dumpEffect(ProtoOutputStream proto, long fieldId,
-                VibrationEffect.Composed effect) {
-            final long token = proto.start(fieldId);
-            for (VibrationEffect.Composition.PrimitiveEffect primitive :
-                    effect.getPrimitiveEffects()) {
-                proto.write(ComposedProto.EFFECT_IDS, primitive.id);
-                proto.write(ComposedProto.EFFECT_SCALES, primitive.scale);
-                proto.write(ComposedProto.DELAYS, primitive.delay);
-            }
+            proto.write(PrimitiveSegmentProto.PRIMITIVE_ID, segment.getPrimitiveId());
+            proto.write(PrimitiveSegmentProto.SCALE, segment.getScale());
+            proto.write(PrimitiveSegmentProto.DELAY, segment.getDelay());
             proto.end(token);
         }
     }
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index 10393f6..f481772 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -18,16 +18,13 @@
 
 import android.content.Context;
 import android.hardware.vibrator.V1_0.EffectStrength;
-import android.os.CombinedVibrationEffect;
 import android.os.IExternalVibratorService;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.os.vibrator.PrebakedSegment;
 import android.util.Slog;
 import android.util.SparseArray;
 
-import java.util.List;
-import java.util.Objects;
-
 /** Controls vibration scaling. */
 final class VibrationScaler {
     private static final String TAG = "VibrationScaler";
@@ -90,43 +87,6 @@
     }
 
     /**
-     * Scale a {@link CombinedVibrationEffect} based on the given usage hint for this vibration.
-     *
-     * @param combinedEffect the effect to be scaled
-     * @param usageHint      one of VibrationAttributes.USAGE_*
-     * @return The same given effect, if no changes were made, or a new
-     * {@link CombinedVibrationEffect} with resolved and scaled amplitude
-     */
-    public <T extends CombinedVibrationEffect> T scale(CombinedVibrationEffect combinedEffect,
-            int usageHint) {
-        if (combinedEffect instanceof CombinedVibrationEffect.Mono) {
-            VibrationEffect effect = ((CombinedVibrationEffect.Mono) combinedEffect).getEffect();
-            return (T) CombinedVibrationEffect.createSynced(scale(effect, usageHint));
-        } else if (combinedEffect instanceof CombinedVibrationEffect.Stereo) {
-            SparseArray<VibrationEffect> effects =
-                    ((CombinedVibrationEffect.Stereo) combinedEffect).getEffects();
-            CombinedVibrationEffect.SyncedCombination combination =
-                    CombinedVibrationEffect.startSynced();
-            for (int i = 0; i < effects.size(); i++) {
-                combination.addVibrator(effects.keyAt(i), scale(effects.valueAt(i), usageHint));
-            }
-            return (T) combination.combine();
-        } else if (combinedEffect instanceof CombinedVibrationEffect.Sequential) {
-            List<CombinedVibrationEffect> effects =
-                    ((CombinedVibrationEffect.Sequential) combinedEffect).getEffects();
-            CombinedVibrationEffect.SequentialCombination combination =
-                    CombinedVibrationEffect.startSequential();
-            for (CombinedVibrationEffect effect : effects) {
-                combination.addNext(scale(effect, usageHint));
-            }
-            return (T) combination.combine();
-        } else {
-            // Unknown combination, return same effect.
-            return (T) combinedEffect;
-        }
-    }
-
-    /**
      * Scale a {@link VibrationEffect} based on the given usage hint for this vibration.
      *
      * @param effect    the effect to be scaled
@@ -135,33 +95,10 @@
      * resolved and scaled amplitude
      */
     public <T extends VibrationEffect> T scale(VibrationEffect effect, int usageHint) {
-        if (effect instanceof VibrationEffect.Prebaked) {
-            // Prebaked effects are always just a direct translation to EffectStrength.
-            int intensity = mSettingsController.getCurrentIntensity(usageHint);
-            int newStrength = intensityToEffectStrength(intensity);
-            VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
-            int strength = prebaked.getEffectStrength();
-            VibrationEffect fallback = prebaked.getFallbackEffect();
-
-            if (fallback != null) {
-                VibrationEffect scaledFallback = scale(fallback, usageHint);
-                if (strength == newStrength && Objects.equals(fallback, scaledFallback)) {
-                    return (T) prebaked;
-                }
-
-                return (T) new VibrationEffect.Prebaked(prebaked.getId(), newStrength,
-                        scaledFallback);
-            } else if (strength == newStrength) {
-                return (T) prebaked;
-            } else {
-                return (T) new VibrationEffect.Prebaked(prebaked.getId(), prebaked.shouldFallback(),
-                        newStrength);
-            }
-        }
-
-        effect = effect.resolve(mDefaultVibrationAmplitude);
         int defaultIntensity = mSettingsController.getDefaultIntensity(usageHint);
         int currentIntensity = mSettingsController.getCurrentIntensity(usageHint);
+        int newEffectStrength = intensityToEffectStrength(currentIntensity);
+        effect = effect.applyEffectStrength(newEffectStrength).resolve(mDefaultVibrationAmplitude);
         ScaleLevel scale = mScaleLevels.get(currentIntensity - defaultIntensity);
 
         if (scale == null) {
@@ -171,7 +108,21 @@
             return (T) effect;
         }
 
-        return effect.scale(scale.factor);
+        return (T) effect.scale(scale.factor);
+    }
+
+    /**
+     * Scale a {@link PrebakedSegment} based on the given usage hint for this vibration.
+     *
+     * @param prebaked  the prebaked segment to be scaled
+     * @param usageHint one of VibrationAttributes.USAGE_*
+     * @return The same segment if no changes were made, or a new {@link PrebakedSegment} with
+     * updated effect strength
+     */
+    public PrebakedSegment scale(PrebakedSegment prebaked, int usageHint) {
+        int currentIntensity = mSettingsController.getCurrentIntensity(usageHint);
+        int newEffectStrength = intensityToEffectStrength(currentIntensity);
+        return prebaked.applyEffectStrength(newEffectStrength);
     }
 
     /** Mapping of Vibrator.VIBRATION_INTENSITY_* values to {@link EffectStrength}. */
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index b90408f..18063de 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -28,6 +28,10 @@
 import android.os.Trace;
 import android.os.VibrationEffect;
 import android.os.WorkSource;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -248,22 +252,26 @@
      * Get the duration the vibrator will be on for given {@code waveform}, starting at {@code
      * startIndex} until the next time it's vibrating amplitude is zero.
      */
-    private static long getVibratorOnDuration(VibrationEffect.Waveform waveform, int startIndex) {
-        long[] timings = waveform.getTimings();
-        int[] amplitudes = waveform.getAmplitudes();
-        int repeatIndex = waveform.getRepeatIndex();
+    private static long getVibratorOnDuration(VibrationEffect.Composed effect, int startIndex) {
+        List<VibrationEffectSegment> segments = effect.getSegments();
+        int segmentCount = segments.size();
+        int repeatIndex = effect.getRepeatIndex();
         int i = startIndex;
         long timing = 0;
-        while (timings[i] == 0 || amplitudes[i] != 0) {
-            timing += timings[i++];
-            if (i >= timings.length) {
-                if (repeatIndex >= 0) {
-                    i = repeatIndex;
-                    // prevent infinite loop
-                    repeatIndex = -1;
-                } else {
-                    break;
-                }
+        while (i < segmentCount) {
+            if (!(segments.get(i) instanceof StepSegment)) {
+                break;
+            }
+            StepSegment stepSegment = (StepSegment) segments.get(i);
+            if (stepSegment.getAmplitude() == 0) {
+                break;
+            }
+            timing += stepSegment.getDuration();
+            i++;
+            if (i == segmentCount && repeatIndex >= 0) {
+                i = repeatIndex;
+                // prevent infinite loop
+                repeatIndex = -1;
             }
             if (i == startIndex) {
                 return 1000;
@@ -620,22 +628,14 @@
         }
 
         private long startVibrating(VibrationEffect effect, List<Step> nextSteps) {
+            VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+            VibrationEffectSegment firstSegment = composed.getSegments().get(0);
             final long duration;
             final long now = SystemClock.uptimeMillis();
-            if (effect instanceof VibrationEffect.OneShot) {
-                VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
-                duration = oneShot.getDuration();
-                // Do NOT set amplitude here. This might be called between prepareSynced and
-                // triggerSynced, so the vibrator is not actually turned on here.
-                // The next steps will handle the amplitude after the vibrator has turned on.
-                controller.on(duration, mVibration.id);
-                nextSteps.add(new VibratorAmplitudeStep(now, controller, oneShot,
-                        now + duration + CALLBACKS_EXTRA_TIMEOUT));
-            } else if (effect instanceof VibrationEffect.Waveform) {
-                VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
+            if (firstSegment instanceof StepSegment) {
                 // Return the full duration of this waveform effect.
-                duration = waveform.getDuration();
-                long onDuration = getVibratorOnDuration(waveform, 0);
+                duration = effect.getDuration();
+                long onDuration = getVibratorOnDuration(composed, 0);
                 if (onDuration > 0) {
                     // Do NOT set amplitude here. This might be called between prepareSynced and
                     // triggerSynced, so the vibrator is not actually turned on here.
@@ -643,19 +643,31 @@
                     controller.on(onDuration, mVibration.id);
                 }
                 long offTime = onDuration > 0 ? now + onDuration + CALLBACKS_EXTRA_TIMEOUT : now;
-                nextSteps.add(new VibratorAmplitudeStep(now, controller, waveform, offTime));
-            } else if (effect instanceof VibrationEffect.Prebaked) {
-                VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
+                nextSteps.add(new VibratorAmplitudeStep(now, controller, composed, offTime));
+            } else if (firstSegment instanceof PrebakedSegment) {
+                PrebakedSegment prebaked = (PrebakedSegment) firstSegment;
+                VibrationEffect fallback = mVibration.getFallback(prebaked.getEffectId());
                 duration = controller.on(prebaked, mVibration.id);
                 if (duration > 0) {
                     nextSteps.add(new VibratorOffStep(now + duration + CALLBACKS_EXTRA_TIMEOUT,
                             controller));
-                } else if (prebaked.getFallbackEffect() != null) {
-                    return startVibrating(prebaked.getFallbackEffect(), nextSteps);
+                } else if (prebaked.shouldFallback() && fallback != null) {
+                    return startVibrating(fallback, nextSteps);
                 }
-            } else if (effect instanceof VibrationEffect.Composed) {
-                VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
-                duration = controller.on(composed, mVibration.id);
+            } else if (firstSegment instanceof PrimitiveSegment) {
+                int segmentCount = composed.getSegments().size();
+                PrimitiveSegment[] primitives = new PrimitiveSegment[segmentCount];
+                for (int i = 0; i < segmentCount; i++) {
+                    VibrationEffectSegment segment = composed.getSegments().get(i);
+                    if (segment instanceof PrimitiveSegment) {
+                        primitives[i] = (PrimitiveSegment) segment;
+                    } else {
+                        primitives[i] = new PrimitiveSegment(
+                                VibrationEffect.Composition.PRIMITIVE_NOOP,
+                                /* scale= */ 1, /* delay= */ 0);
+                    }
+                }
+                duration = controller.on(primitives, mVibration.id);
                 if (duration > 0) {
                     nextSteps.add(new VibratorOffStep(now + duration + CALLBACKS_EXTRA_TIMEOUT,
                             controller));
@@ -713,33 +725,22 @@
     /** Represents a step to change the amplitude of the vibrator. */
     private final class VibratorAmplitudeStep extends Step {
         public final VibratorController controller;
-        public final VibrationEffect.Waveform waveform;
+        public final VibrationEffect.Composed effect;
         public final int currentIndex;
-        public final long expectedVibratorStopTime;
 
         private long mNextVibratorStopTime;
 
         VibratorAmplitudeStep(long startTime, VibratorController controller,
-                VibrationEffect.OneShot oneShot, long expectedVibratorStopTime) {
-            this(startTime, controller,
-                    (VibrationEffect.Waveform) VibrationEffect.createWaveform(
-                            new long[]{oneShot.getDuration()}, new int[]{oneShot.getAmplitude()},
-                            /* repeat= */ -1),
-                    expectedVibratorStopTime);
+                VibrationEffect.Composed effect, long expectedVibratorStopTime) {
+            this(startTime, controller, effect, /* index= */ 0, expectedVibratorStopTime);
         }
 
         VibratorAmplitudeStep(long startTime, VibratorController controller,
-                VibrationEffect.Waveform waveform, long expectedVibratorStopTime) {
-            this(startTime, controller, waveform, /* index= */ 0, expectedVibratorStopTime);
-        }
-
-        VibratorAmplitudeStep(long startTime, VibratorController controller,
-                VibrationEffect.Waveform waveform, int index, long expectedVibratorStopTime) {
+                VibrationEffect.Composed effect, int index, long expectedVibratorStopTime) {
             super(startTime);
             this.controller = controller;
-            this.waveform = waveform;
+            this.effect = effect;
             this.currentIndex = index;
-            this.expectedVibratorStopTime = expectedVibratorStopTime;
             mNextVibratorStopTime = expectedVibratorStopTime;
         }
 
@@ -759,11 +760,16 @@
                     long latency = SystemClock.uptimeMillis() - startTime;
                     Slog.d(TAG, "Running amplitude step with " + latency + "ms latency.");
                 }
-                if (waveform.getTimings()[currentIndex] == 0) {
+                VibrationEffectSegment segment = effect.getSegments().get(currentIndex);
+                if (!(segment instanceof StepSegment)) {
+                    return nextSteps();
+                }
+                StepSegment stepSegment = (StepSegment) segment;
+                if (stepSegment.getDuration() == 0) {
                     // Skip waveform entries with zero timing.
                     return nextSteps();
                 }
-                int amplitude = waveform.getAmplitudes()[currentIndex];
+                float amplitude = stepSegment.getAmplitude();
                 if (amplitude == 0) {
                     stopVibrating();
                     return nextSteps();
@@ -771,7 +777,7 @@
                 if (startTime >= mNextVibratorStopTime) {
                     // Vibrator has stopped. Turn vibrator back on for the duration of another
                     // cycle before setting the amplitude.
-                    long onDuration = getVibratorOnDuration(waveform, currentIndex);
+                    long onDuration = getVibratorOnDuration(effect, currentIndex);
                     if (onDuration > 0) {
                         startVibrating(onDuration);
                         mNextVibratorStopTime =
@@ -806,7 +812,7 @@
             controller.on(duration, mVibration.id);
         }
 
-        private void changeAmplitude(int amplitude) {
+        private void changeAmplitude(float amplitude) {
             if (DEBUG) {
                 Slog.d(TAG, "Amplitude changed on vibrator " + controller.getVibratorInfo().getId()
                         + " to " + amplitude);
@@ -816,16 +822,16 @@
 
         @NonNull
         private List<Step> nextSteps() {
-            long nextStartTime = startTime + waveform.getTimings()[currentIndex];
+            long nextStartTime = startTime + effect.getSegments().get(currentIndex).getDuration();
             int nextIndex = currentIndex + 1;
-            if (nextIndex >= waveform.getTimings().length) {
-                nextIndex = waveform.getRepeatIndex();
+            if (nextIndex >= effect.getSegments().size()) {
+                nextIndex = effect.getRepeatIndex();
             }
-            if (nextIndex < 0) {
-                return Arrays.asList(new VibratorOffStep(nextStartTime, controller));
-            }
-            return Arrays.asList(new VibratorAmplitudeStep(nextStartTime, controller, waveform,
-                    nextIndex, mNextVibratorStopTime));
+            Step nextStep = nextIndex < 0
+                    ? new VibratorOffStep(nextStartTime, controller)
+                    : new VibratorAmplitudeStep(nextStartTime, controller, effect, nextIndex,
+                            mNextVibratorStopTime);
+            return Arrays.asList(nextStep);
         }
     }
 
@@ -909,13 +915,13 @@
         private long calculateRequiredSyncCapabilities(SparseArray<VibrationEffect> effects) {
             long prepareCap = 0;
             for (int i = 0; i < effects.size(); i++) {
-                VibrationEffect effect = effects.valueAt(i);
-                if (effect instanceof VibrationEffect.OneShot
-                        || effect instanceof VibrationEffect.Waveform) {
+                VibrationEffect.Composed composed = (VibrationEffect.Composed) effects.valueAt(i);
+                VibrationEffectSegment firstSegment = composed.getSegments().get(0);
+                if (firstSegment instanceof StepSegment) {
                     prepareCap |= IVibratorManager.CAP_PREPARE_ON;
-                } else if (effect instanceof VibrationEffect.Prebaked) {
+                } else if (firstSegment instanceof PrebakedSegment) {
                     prepareCap |= IVibratorManager.CAP_PREPARE_PERFORM;
-                } else if (effect instanceof VibrationEffect.Composed) {
+                } else if (firstSegment instanceof PrimitiveSegment) {
                     prepareCap |= IVibratorManager.CAP_PREPARE_COMPOSE;
                 }
             }
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index e3dc70b..66200b3 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -22,8 +22,9 @@
 import android.os.IVibratorStateListener;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
-import android.os.VibrationEffect;
 import android.os.VibratorInfo;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
@@ -156,21 +157,22 @@
      * Update the predefined vibration effect saved with given id. This will remove the saved effect
      * if given {@code effect} is {@code null}.
      */
-    public void updateAlwaysOn(int id, @Nullable VibrationEffect.Prebaked effect) {
+    public void updateAlwaysOn(int id, @Nullable PrebakedSegment prebaked) {
         if (!mVibratorInfo.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
             return;
         }
         synchronized (mLock) {
-            if (effect == null) {
+            if (prebaked == null) {
                 mNativeWrapper.alwaysOnDisable(id);
             } else {
-                mNativeWrapper.alwaysOnEnable(id, effect.getId(), effect.getEffectStrength());
+                mNativeWrapper.alwaysOnEnable(id, prebaked.getEffectId(),
+                        prebaked.getEffectStrength());
             }
         }
     }
 
     /** Set the vibration amplitude. This will NOT affect the state of {@link #isVibrating()}. */
-    public void setAmplitude(int amplitude) {
+    public void setAmplitude(float amplitude) {
         synchronized (mLock) {
             if (mVibratorInfo.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
                 mNativeWrapper.setAmplitude(amplitude);
@@ -199,10 +201,10 @@
      *
      * @return The duration of the effect playing, or 0 if unsupported.
      */
-    public long on(VibrationEffect.Prebaked effect, long vibrationId) {
+    public long on(PrebakedSegment prebaked, long vibrationId) {
         synchronized (mLock) {
-            long duration = mNativeWrapper.perform(effect.getId(), effect.getEffectStrength(),
-                    vibrationId);
+            long duration = mNativeWrapper.perform(prebaked.getEffectId(),
+                    prebaked.getEffectStrength(), vibrationId);
             if (duration > 0) {
                 notifyVibratorOnLocked();
             }
@@ -211,21 +213,18 @@
     }
 
     /**
-     * Plays composited vibration effect, using {@code vibrationId} or completion callback to
-     * {@link OnVibrationCompleteListener}.
+     * Plays a composition of vibration primitives, using {@code vibrationId} or completion callback
+     * to {@link OnVibrationCompleteListener}.
      *
      * <p>This will affect the state of {@link #isVibrating()}.
      *
      * @return The duration of the effect playing, or 0 if unsupported.
      */
-    public long on(VibrationEffect.Composed effect, long vibrationId) {
+    public long on(PrimitiveSegment[] primitives, long vibrationId) {
         if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
             return 0;
         }
         synchronized (mLock) {
-            VibrationEffect.Composition.PrimitiveEffect[] primitives =
-                    effect.getPrimitiveEffects().toArray(
-                            new VibrationEffect.Composition.PrimitiveEffect[0]);
             long duration = mNativeWrapper.compose(primitives, vibrationId);
             if (duration > 0) {
                 notifyVibratorOnLocked();
@@ -313,13 +312,13 @@
         private static native boolean isAvailable(long nativePtr);
         private static native void on(long nativePtr, long milliseconds, long vibrationId);
         private static native void off(long nativePtr);
-        private static native void setAmplitude(long nativePtr, int amplitude);
+        private static native void setAmplitude(long nativePtr, float amplitude);
         private static native int[] getSupportedEffects(long nativePtr);
         private static native int[] getSupportedPrimitives(long nativePtr);
-        private static native long performEffect(
-                long nativePtr, long effect, long strength, long vibrationId);
-        private static native long performComposedEffect(long nativePtr,
-                VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId);
+        private static native long performEffect(long nativePtr, long effect, long strength,
+                long vibrationId);
+        private static native long performComposedEffect(long nativePtr, PrimitiveSegment[] effect,
+                long vibrationId);
         private static native void setExternalControl(long nativePtr, boolean enabled);
         private static native long getCapabilities(long nativePtr);
         private static native void alwaysOnEnable(long nativePtr, long id, long effect,
@@ -359,7 +358,7 @@
         }
 
         /** Sets the amplitude for the vibrator to run. */
-        public void setAmplitude(int amplitude) {
+        public void setAmplitude(float amplitude) {
             setAmplitude(mNativePtr, amplitude);
         }
 
@@ -379,9 +378,8 @@
         }
 
         /** Turns vibrator on to perform one of the supported composed effects. */
-        public long compose(
-                VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId) {
-            return performComposedEffect(mNativePtr, effect, vibrationId);
+        public long compose(PrimitiveSegment[] primitives, long vibrationId) {
+            return performComposedEffect(mNativePtr, primitives, vibrationId);
         }
 
         /** Enabled the device vibrator to be controlled by another service. */
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index c9751bb..5fd1d7a 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -48,6 +48,8 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.VibratorInfo;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.VibrationEffectSegment;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
@@ -312,7 +314,7 @@
             }
             attrs = fixupVibrationAttributes(attrs);
             synchronized (mLock) {
-                SparseArray<VibrationEffect.Prebaked> effects = fixupAlwaysOnEffectsLocked(effect);
+                SparseArray<PrebakedSegment> effects = fixupAlwaysOnEffectsLocked(effect);
                 if (effects == null) {
                     // Invalid effects set in CombinedVibrationEffect, or always-on capability is
                     // missing on individual vibrators.
@@ -347,8 +349,7 @@
             attrs = fixupVibrationAttributes(attrs);
             Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs,
                     uid, opPkg, reason);
-            // Update with fixed up effect to keep the original effect in Vibration for debugging.
-            vib.updateEffect(fixupVibrationEffect(effect));
+            fillVibrationFallbacks(vib, effect);
 
             synchronized (mLock) {
                 Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib);
@@ -476,7 +477,7 @@
     private void updateAlwaysOnLocked(AlwaysOnVibration vib) {
         for (int i = 0; i < vib.effects.size(); i++) {
             VibratorController vibrator = mVibrators.get(vib.effects.keyAt(i));
-            VibrationEffect.Prebaked effect = vib.effects.valueAt(i);
+            PrebakedSegment effect = vib.effects.valueAt(i);
             if (vibrator == null) {
                 continue;
             }
@@ -496,7 +497,7 @@
     private Vibration.Status startVibrationLocked(Vibration vib) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
         try {
-            vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage()));
+            vib.updateEffects(effect -> mVibrationScaler.scale(effect, vib.attrs.getUsage()));
             boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
                     vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs);
             if (inputDevicesAvailable) {
@@ -757,43 +758,38 @@
      * Sets fallback effects to all prebaked ones in given combination of effects, based on {@link
      * VibrationSettings#getFallbackEffect}.
      */
-    private CombinedVibrationEffect fixupVibrationEffect(CombinedVibrationEffect effect) {
+    private void fillVibrationFallbacks(Vibration vib, CombinedVibrationEffect effect) {
         if (effect instanceof CombinedVibrationEffect.Mono) {
-            return CombinedVibrationEffect.createSynced(
-                    fixupVibrationEffect(((CombinedVibrationEffect.Mono) effect).getEffect()));
+            fillVibrationFallbacks(vib, ((CombinedVibrationEffect.Mono) effect).getEffect());
         } else if (effect instanceof CombinedVibrationEffect.Stereo) {
-            CombinedVibrationEffect.SyncedCombination combination =
-                    CombinedVibrationEffect.startSynced();
             SparseArray<VibrationEffect> effects =
                     ((CombinedVibrationEffect.Stereo) effect).getEffects();
             for (int i = 0; i < effects.size(); i++) {
-                combination.addVibrator(effects.keyAt(i), fixupVibrationEffect(effects.valueAt(i)));
+                fillVibrationFallbacks(vib, effects.valueAt(i));
             }
-            return combination.combine();
         } else if (effect instanceof CombinedVibrationEffect.Sequential) {
-            CombinedVibrationEffect.SequentialCombination combination =
-                    CombinedVibrationEffect.startSequential();
             List<CombinedVibrationEffect> effects =
                     ((CombinedVibrationEffect.Sequential) effect).getEffects();
-            for (CombinedVibrationEffect e : effects) {
-                combination.addNext(fixupVibrationEffect(e));
+            for (int i = 0; i < effects.size(); i++) {
+                fillVibrationFallbacks(vib, effects.get(i));
             }
-            return combination.combine();
         }
-        return effect;
     }
 
-    private VibrationEffect fixupVibrationEffect(VibrationEffect effect) {
-        if (effect instanceof VibrationEffect.Prebaked
-                && ((VibrationEffect.Prebaked) effect).shouldFallback()) {
-            VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
-            VibrationEffect fallback = mVibrationSettings.getFallbackEffect(prebaked.getId());
-            if (fallback != null) {
-                return new VibrationEffect.Prebaked(prebaked.getId(), prebaked.getEffectStrength(),
-                        fallback);
+    private void fillVibrationFallbacks(Vibration vib, VibrationEffect effect) {
+        VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+        int segmentCount = composed.getSegments().size();
+        for (int i = 0; i < segmentCount; i++) {
+            VibrationEffectSegment segment = composed.getSegments().get(i);
+            if (segment instanceof PrebakedSegment) {
+                PrebakedSegment prebaked = (PrebakedSegment) segment;
+                VibrationEffect fallback = mVibrationSettings.getFallbackEffect(
+                        prebaked.getEffectId());
+                if (prebaked.shouldFallback() && fallback != null) {
+                    vib.addFallback(prebaked.getEffectId(), fallback);
+                }
             }
         }
-        return effect;
     }
 
     /**
@@ -819,7 +815,7 @@
 
     @GuardedBy("mLock")
     @Nullable
-    private SparseArray<VibrationEffect.Prebaked> fixupAlwaysOnEffectsLocked(
+    private SparseArray<PrebakedSegment> fixupAlwaysOnEffectsLocked(
             CombinedVibrationEffect effect) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "fixupAlwaysOnEffectsLocked");
         try {
@@ -833,17 +829,17 @@
                 // Only synced combinations can be used for always-on effects.
                 return null;
             }
-            SparseArray<VibrationEffect.Prebaked> result = new SparseArray<>();
+            SparseArray<PrebakedSegment> result = new SparseArray<>();
             for (int i = 0; i < effects.size(); i++) {
-                VibrationEffect prebaked = effects.valueAt(i);
-                if (!(prebaked instanceof VibrationEffect.Prebaked)) {
+                PrebakedSegment prebaked = extractPrebakedSegment(effects.valueAt(i));
+                if (prebaked == null) {
                     Slog.e(TAG, "Only prebaked effects supported for always-on.");
                     return null;
                 }
                 int vibratorId = effects.keyAt(i);
                 VibratorController vibrator = mVibrators.get(vibratorId);
                 if (vibrator != null && vibrator.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
-                    result.put(vibratorId, (VibrationEffect.Prebaked) prebaked);
+                    result.put(vibratorId, prebaked);
                 }
             }
             if (result.size() == 0) {
@@ -855,6 +851,20 @@
         }
     }
 
+    @Nullable
+    private static PrebakedSegment extractPrebakedSegment(VibrationEffect effect) {
+        if (effect instanceof VibrationEffect.Composed) {
+            VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+            if (composed.getSegments().size() == 1) {
+                VibrationEffectSegment segment = composed.getSegments().get(0);
+                if (segment instanceof PrebakedSegment) {
+                    return (PrebakedSegment) segment;
+                }
+            }
+        }
+        return null;
+    }
+
     /**
      * Check given mode, one of the AppOpsManager.MODE_*, against {@link VibrationAttributes} to
      * allow bypassing {@link AppOpsManager} checks.
@@ -1008,10 +1018,10 @@
         public final int uid;
         public final String opPkg;
         public final VibrationAttributes attrs;
-        public final SparseArray<VibrationEffect.Prebaked> effects;
+        public final SparseArray<PrebakedSegment> effects;
 
         AlwaysOnVibration(int alwaysOnId, int uid, String opPkg, VibrationAttributes attrs,
-                SparseArray<VibrationEffect.Prebaked> effects) {
+                SparseArray<PrebakedSegment> effects) {
             this.alwaysOnId = alwaysOnId;
             this.uid = uid;
             this.opPkg = opPkg;
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index e6d37b6..b947c88 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1511,10 +1511,15 @@
             IBinder topFocusedWindowToken = null;
 
             synchronized (mService.mGlobalLock) {
-                // Do not send the windows if there is no top focus as
-                // the window manager is still looking for where to put it.
-                // We will do the work when we get a focus change callback.
-                final WindowState topFocusedWindowState = getTopFocusWindow();
+                // If there is a recents animation running, then use the animation target as the
+                // top window state. Otherwise,do not send the windows if there is no top focus as
+                // the window manager is still looking for where to put it. We will do the work when
+                // we get a focus change callback.
+                final RecentsAnimationController controller =
+                        mService.getRecentsAnimationController();
+                final WindowState topFocusedWindowState = controller != null
+                        ? controller.getTargetAppMainWindow()
+                        : getTopFocusWindow();
                 if (topFocusedWindowState == null) {
                     if (DEBUG) {
                         Slog.d(LOG_TAG, "top focused window is null, compute it again later");
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 3a0eb39..dc69175 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -287,6 +287,11 @@
             if (mLastLaunchedActivity == r) {
                 return;
             }
+            if (mLastLaunchedActivity != null) {
+                // Transfer the launch cookie because it is a consecutive launch event.
+                r.mLaunchCookie = mLastLaunchedActivity.mLaunchCookie;
+                mLastLaunchedActivity.mLaunchCookie = null;
+            }
             mLastLaunchedActivity = r;
             if (!r.noDisplay && !r.isReportedDrawn()) {
                 if (DEBUG_METRICS) Slog.i(TAG, "Add pending draw " + r);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 7c23661..eadfbe2 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -6790,7 +6790,12 @@
             // The app bounds hasn't been computed yet.
             return false;
         }
-        final Configuration parentConfig = getParent().getConfiguration();
+        final WindowContainer parent = getParent();
+        if (parent == null) {
+            // The parent of detached Activity can be null.
+            return false;
+        }
+        final Configuration parentConfig = parent.getConfiguration();
         // Although colorMode, screenLayout, smallestScreenWidthDp are also fixed, generally these
         // fields should be changed with density and bounds, so here only compares the most
         // significant field.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 060323c..12c67bb 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -607,4 +607,10 @@
          */
         void commit() throws RemoteException;
     }
+
+    /**
+     * A utility method to check AppOps and PackageManager for SYSTEM_ALERT_WINDOW permission.
+     */
+    public abstract boolean hasSystemAlertWindowPermission(int callingUid, int callingPid,
+            String callingPackage);
 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 09f5c93..29c5cec 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -251,6 +251,7 @@
 import com.android.server.am.ActivityManagerService;
 import com.android.server.am.ActivityManagerServiceDumpProcessesProto;
 import com.android.server.am.AppTimeTracker;
+import com.android.server.am.AssistDataRequester;
 import com.android.server.am.BaseErrorDialog;
 import com.android.server.am.PendingIntentController;
 import com.android.server.am.PendingIntentRecord;
@@ -933,7 +934,8 @@
         return getUserManager().hasUserRestriction(restriction, userId);
     }
 
-    boolean hasSystemAlertWindowPermission(int callingUid, int callingPid, String callingPackage) {
+    boolean hasSystemAlertWindowPermission(int callingUid, int callingPid,
+            String callingPackage) {
         final int mode = getAppOpsManager().noteOpNoThrow(AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
                 callingUid, callingPackage, /* featureId */ null, "");
         if (mode == AppOpsManager.MODE_DEFAULT) {
@@ -2827,10 +2829,45 @@
 
     @Override
     public boolean requestAssistContextExtras(int requestType, IAssistDataReceiver receiver,
-            Bundle receiverExtras, IBinder activityToken, boolean focused, boolean newSessionId) {
+            Bundle receiverExtras, IBinder activityToken, boolean checkActivityIsTop,
+            boolean newSessionId) {
         return enqueueAssistContext(requestType, null, null, receiver, receiverExtras,
-                activityToken, focused, newSessionId, UserHandle.getCallingUserId(), null,
-                PENDING_ASSIST_EXTRAS_LONG_TIMEOUT, 0) != null;
+                activityToken, checkActivityIsTop, newSessionId, UserHandle.getCallingUserId(),
+                null, PENDING_ASSIST_EXTRAS_LONG_TIMEOUT, 0) != null;
+    }
+
+    @Override
+    public boolean requestAssistDataForTask(IAssistDataReceiver receiver, int taskId,
+            String callingPackageName) {
+        mAmInternal.enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
+                "requestAssistDataForTask()");
+        final long callingId = Binder.clearCallingIdentity();
+        LocalService.ActivityTokens tokens = null;
+        try {
+            tokens = mInternal.getTopActivityForTask(taskId);
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+        if (tokens == null) {
+            Log.e(TAG, "Could not find activity for task " + taskId);
+            return false;
+        }
+
+        final AssistDataReceiverProxy proxy =
+                new AssistDataReceiverProxy(receiver, callingPackageName);
+        Object lock = new Object();
+        AssistDataRequester requester = new AssistDataRequester(mContext, mWindowManager,
+                getAppOpsManager(), proxy, lock, AppOpsManager.OP_ASSIST_STRUCTURE,
+                AppOpsManager.OP_NONE);
+
+        List<IBinder> topActivityToken = new ArrayList<>();
+        topActivityToken.add(tokens.getActivityToken());
+        requester.requestAssistData(topActivityToken, true /* fetchData */,
+                false /* fetchScreenshot */, true /* allowFetchData */,
+                false /* allowFetchScreenshot*/, true /* ignoreFocusCheck */,
+                Binder.getCallingUid(), callingPackageName);
+
+        return true;
     }
 
     @Override
@@ -2844,7 +2881,7 @@
     @Override
     public Bundle getAssistContextExtras(int requestType) {
         PendingAssistExtras pae = enqueueAssistContext(requestType, null, null, null,
-                null, null, true /* focused */, true /* newSessionId */,
+                null, null, true /* checkActivityIsTop */, true /* newSessionId */,
                 UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_TIMEOUT, 0);
         if (pae == null) {
             return null;
@@ -3047,8 +3084,8 @@
 
     private PendingAssistExtras enqueueAssistContext(int requestType, Intent intent, String hint,
             IAssistDataReceiver receiver, Bundle receiverExtras, IBinder activityToken,
-            boolean focused, boolean newSessionId, int userHandle, Bundle args, long timeout,
-            int flags) {
+            boolean checkActivityIsTop, boolean newSessionId, int userHandle, Bundle args,
+            long timeout, int flags) {
         mAmInternal.enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
                 "enqueueAssistContext()");
 
@@ -3064,7 +3101,7 @@
                 Slog.w(TAG, "getAssistContextExtras failed: no process for " + activity);
                 return null;
             }
-            if (focused) {
+            if (checkActivityIsTop) {
                 if (activityToken != null) {
                     ActivityRecord caller = ActivityRecord.forTokenLocked(activityToken);
                     if (activity != caller) {
@@ -6370,6 +6407,13 @@
                 return new PackageConfigurationUpdaterImpl(Binder.getCallingPid());
             }
         }
+
+        @Override
+        public boolean hasSystemAlertWindowPermission(int callingUid, int callingPid,
+                String callingPackage) {
+            return ActivityTaskManagerService.this.hasSystemAlertWindowPermission(callingUid,
+                    callingPid, callingPackage);
+        }
     }
 
     final class PackageConfigurationUpdaterImpl implements
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 6d24105..6072a06 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -516,7 +516,7 @@
 
     /** The delay to avoid toggling the animation quickly. */
     private static final long FIXED_ROTATION_HIDE_ANIMATION_DEBOUNCE_DELAY_MS = 250;
-    private FixedRotationAnimationController mFixedRotationAnimationController;
+    private FadeRotationAnimationController mFadeRotationAnimationController;
 
     final FixedRotationTransitionListener mFixedRotationTransitionListener =
             new FixedRotationTransitionListener();
@@ -1590,8 +1590,8 @@
     }
 
     @VisibleForTesting
-    @Nullable FixedRotationAnimationController getFixedRotationAnimationController() {
-        return mFixedRotationAnimationController;
+    @Nullable FadeRotationAnimationController getFadeRotationAnimationController() {
+        return mFadeRotationAnimationController;
     }
 
     void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r) {
@@ -1601,13 +1601,13 @@
     void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r, int rotation) {
         if (mFixedRotationLaunchingApp == null && r != null) {
             mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation);
-            startFixedRotationAnimation(
+            startFadeRotationAnimation(
                     // Delay the hide animation to avoid blinking by clicking navigation bar that
                     // may toggle fixed rotation in a short time.
                     r == mFixedRotationTransitionListener.mAnimatingRecents /* shouldDebounce */);
         } else if (mFixedRotationLaunchingApp != null && r == null) {
             mWmService.mDisplayNotificationController.dispatchFixedRotationFinished(this);
-            finishFixedRotationAnimationIfPossible();
+            finishFadeRotationAnimationIfPossible();
         }
         mFixedRotationLaunchingApp = r;
     }
@@ -1714,12 +1714,12 @@
      *
      * @return {@code true} if the animation is executed right now.
      */
-    private boolean startFixedRotationAnimation(boolean shouldDebounce) {
+    private boolean startFadeRotationAnimation(boolean shouldDebounce) {
         if (shouldDebounce) {
             mWmService.mH.postDelayed(() -> {
                 synchronized (mWmService.mGlobalLock) {
                     if (mFixedRotationLaunchingApp != null
-                            && startFixedRotationAnimation(false /* shouldDebounce */)) {
+                            && startFadeRotationAnimation(false /* shouldDebounce */)) {
                         // Apply the transaction so the animation leash can take effect immediately.
                         getPendingTransaction().apply();
                     }
@@ -1727,23 +1727,41 @@
             }, FIXED_ROTATION_HIDE_ANIMATION_DEBOUNCE_DELAY_MS);
             return false;
         }
-        if (mFixedRotationAnimationController == null) {
-            mFixedRotationAnimationController = new FixedRotationAnimationController(this);
-            mFixedRotationAnimationController.hide();
+        if (mFadeRotationAnimationController == null) {
+            mFadeRotationAnimationController = new FadeRotationAnimationController(this);
+            mFadeRotationAnimationController.hide();
             return true;
         }
         return false;
     }
 
     /** Re-show the previously hidden windows if all seamless rotated windows are done. */
-    void finishFixedRotationAnimationIfPossible() {
-        final FixedRotationAnimationController controller = mFixedRotationAnimationController;
+    void finishFadeRotationAnimationIfPossible() {
+        final FadeRotationAnimationController controller = mFadeRotationAnimationController;
         if (controller != null && !mDisplayRotation.hasSeamlessRotatingWindow()) {
             controller.show();
-            mFixedRotationAnimationController = null;
+            mFadeRotationAnimationController = null;
         }
     }
 
+    /** Shows the given window which may be hidden for screen frozen. */
+    void finishFadeRotationAnimation(WindowState w) {
+        final FadeRotationAnimationController controller = mFadeRotationAnimationController;
+        if (controller != null && controller.show(w.mToken)) {
+            mFadeRotationAnimationController = null;
+        }
+    }
+
+    /** Returns {@code true} if the display should wait for the given window to stop freezing. */
+    boolean waitForUnfreeze(WindowState w) {
+        if (w.mForceSeamlesslyRotate) {
+            // The window should look no different before and after rotation.
+            return false;
+        }
+        final FadeRotationAnimationController controller = mFadeRotationAnimationController;
+        return controller == null || !controller.isTargetToken(w.mToken);
+    }
+
     void notifyInsetsChanged(Consumer<WindowState> dispatchInsetsChanged) {
         if (mFixedRotationLaunchingApp != null) {
             // The insets state of fixed rotation app is a rotated copy. Make sure the visibilities
@@ -2964,6 +2982,13 @@
             mScreenRotationAnimation.kill();
         }
         mScreenRotationAnimation = screenRotationAnimation;
+
+        // Hide the windows which are not significant in rotation animation. So that the windows
+        // don't need to block the unfreeze time.
+        if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()
+                && mFadeRotationAnimationController == null) {
+            startFadeRotationAnimation(false /* shouldDebounce */);
+        }
     }
 
     public ScreenRotationAnimation getRotationAnimation() {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index d0e4c40..6046cc6 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -626,7 +626,7 @@
         }, true /* traverseTopToBottom */);
         mSeamlessRotationCount = 0;
         mRotatingSeamlessly = false;
-        mDisplayContent.finishFixedRotationAnimationIfPossible();
+        mDisplayContent.finishFadeRotationAnimationIfPossible();
     }
 
     private void prepareSeamlessRotation() {
@@ -717,7 +717,7 @@
                     "Performing post-rotate rotation after seamless rotation");
             // Finish seamless rotation.
             mRotatingSeamlessly = false;
-            mDisplayContent.finishFixedRotationAnimationIfPossible();
+            mDisplayContent.finishFadeRotationAnimationIfPossible();
 
             updateRotationAndSendNewConfigIfChanged();
         }
diff --git a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
new file mode 100644
index 0000000..5ee6928
--- /dev/null
+++ b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
+
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+
+/**
+ * Controller to fade out and in windows when the display is changing rotation. It can be used for
+ * both fixed rotation and normal rotation to hide some non-activity windows. The caller should show
+ * the windows until they are drawn with the new rotation.
+ */
+public class FadeRotationAnimationController extends FadeAnimationController {
+
+    private final ArrayList<WindowToken> mTargetWindowTokens = new ArrayList<>();
+    private final WindowManagerService mService;
+    /** If non-null, it usually indicates that there will be a screen rotation animation. */
+    private final Runnable mFrozenTimeoutRunnable;
+    private final WindowToken mNavBarToken;
+
+    public FadeRotationAnimationController(DisplayContent displayContent) {
+        super(displayContent);
+        mService = displayContent.mWmService;
+        mFrozenTimeoutRunnable = mService.mDisplayFrozen ? () -> {
+            synchronized (mService.mGlobalLock) {
+                displayContent.finishFadeRotationAnimationIfPossible();
+                mService.mWindowPlacerLocked.performSurfacePlacement();
+            }
+        } : null;
+        final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
+        final WindowState navigationBar = displayPolicy.getNavigationBar();
+        if (navigationBar != null) {
+            mNavBarToken = navigationBar.mToken;
+            final RecentsAnimationController controller = mService.getRecentsAnimationController();
+            final boolean navBarControlledByRecents =
+                    controller != null && controller.isNavigationBarAttachedToApp();
+            // Do not animate movable navigation bar (e.g. non-gesture mode) or when the navigation
+            // bar is currently controlled by recents animation.
+            if (!displayPolicy.navigationBarCanMove() && !navBarControlledByRecents) {
+                mTargetWindowTokens.add(mNavBarToken);
+            }
+        } else {
+            mNavBarToken = null;
+        }
+        displayContent.forAllWindows(w -> {
+            if (w.mActivityRecord == null && w.mHasSurface && !w.mForceSeamlesslyRotate
+                    && !w.mIsWallpaper && !w.mIsImWindow && w != navigationBar) {
+                mTargetWindowTokens.add(w.mToken);
+            }
+        }, true /* traverseTopToBottom */);
+    }
+
+    /** Applies show animation on the previously hidden window tokens. */
+    void show() {
+        for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
+            final WindowToken windowToken = mTargetWindowTokens.get(i);
+            fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
+        }
+        mTargetWindowTokens.clear();
+        if (mFrozenTimeoutRunnable != null) {
+            mService.mH.removeCallbacks(mFrozenTimeoutRunnable);
+        }
+    }
+
+    /**
+     * Returns {@code true} if all target windows are shown. It only takes effects if this
+     * controller is created for normal rotation.
+     */
+    boolean show(WindowToken token) {
+        if (mFrozenTimeoutRunnable != null && mTargetWindowTokens.remove(token)) {
+            fadeWindowToken(true /* show */, token, ANIMATION_TYPE_FIXED_TRANSFORM);
+            if (mTargetWindowTokens.isEmpty()) {
+                mService.mH.removeCallbacks(mFrozenTimeoutRunnable);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** Applies hide animation on the window tokens which may be seamlessly rotated later. */
+    void hide() {
+        for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
+            final WindowToken windowToken = mTargetWindowTokens.get(i);
+            fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
+        }
+        if (mFrozenTimeoutRunnable != null) {
+            mService.mH.postDelayed(mFrozenTimeoutRunnable,
+                    WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION);
+        }
+    }
+
+    /** Returns {@code true} if the window is handled by this controller. */
+    boolean isTargetToken(WindowToken token) {
+        return token == mNavBarToken || mTargetWindowTokens.contains(token);
+    }
+
+    @Override
+    public Animation getFadeInAnimation() {
+        if (mFrozenTimeoutRunnable != null) {
+            // Use a shorter animation so it is easier to align with screen rotation animation.
+            return AnimationUtils.loadAnimation(mContext, R.anim.screen_rotate_0_enter);
+        }
+        return super.getFadeInAnimation();
+    }
+
+    @Override
+    public Animation getFadeOutAnimation() {
+        if (mFrozenTimeoutRunnable != null) {
+            // Hide the window immediately because screen should have been covered by screenshot.
+            return new AlphaAnimation(0 /* fromAlpha */, 0 /* toAlpha */);
+        }
+        return super.getFadeOutAnimation();
+    }
+}
diff --git a/services/core/java/com/android/server/wm/FixedRotationAnimationController.java b/services/core/java/com/android/server/wm/FixedRotationAnimationController.java
deleted file mode 100644
index aa73170..0000000
--- a/services/core/java/com/android/server/wm/FixedRotationAnimationController.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
-
-import java.util.ArrayList;
-
-/**
- * Controller to fade out and in system ui when applying a fixed rotation transform to a window
- * token.
- *
- * The system bars will be fade out when the fixed rotation transform starts and will be fade in
- * once all surfaces have been rotated.
- */
-public class FixedRotationAnimationController extends FadeAnimationController {
-
-    private final WindowState mStatusBar;
-    private final WindowState mNavigationBar;
-    private final ArrayList<WindowToken> mAnimatedWindowToken = new ArrayList<>(2);
-
-    public FixedRotationAnimationController(DisplayContent displayContent) {
-        super(displayContent);
-        final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
-        mStatusBar = displayPolicy.getStatusBar();
-
-        final RecentsAnimationController controller =
-                displayContent.mWmService.getRecentsAnimationController();
-        final boolean navBarControlledByRecents =
-                controller != null && controller.isNavigationBarAttachedToApp();
-        // Do not animate movable navigation bar (e.g. non-gesture mode) or when the navigation bar
-        // is currently controlled by recents animation.
-        mNavigationBar = !displayPolicy.navigationBarCanMove()
-                && !navBarControlledByRecents ? displayPolicy.getNavigationBar() : null;
-    }
-
-    /** Applies show animation on the previously hidden window tokens. */
-    void show() {
-        for (int i = mAnimatedWindowToken.size() - 1; i >= 0; i--) {
-            final WindowToken windowToken = mAnimatedWindowToken.get(i);
-            fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
-        }
-    }
-
-    /** Applies hide animation on the window tokens which may be seamlessly rotated later. */
-    void hide() {
-        if (mNavigationBar != null) {
-            fadeWindowToken(false /* show */, mNavigationBar.mToken,
-                    ANIMATION_TYPE_FIXED_TRANSFORM);
-        }
-        if (mStatusBar != null) {
-            fadeWindowToken(false /* show */, mStatusBar.mToken,
-                    ANIMATION_TYPE_FIXED_TRANSFORM);
-        }
-    }
-
-    @Override
-    public void fadeWindowToken(boolean show, WindowToken windowToken, int animationType) {
-        super.fadeWindowToken(show, windowToken, animationType);
-        mAnimatedWindowToken.add(windowToken);
-    }
-}
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 465042d..cda3fda 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -25,6 +25,7 @@
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.SyncRtSurfaceTransactionApplier.applyParams;
+import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
 
@@ -458,10 +459,8 @@
         InsetsPolicyAnimationControlCallbacks mControlCallbacks;
 
         InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback, int types) {
-
-            super(show, false /* hasCallbacks */, types, false /* disable */,
-                    (int) (mDisplayContent.getDisplayMetrics().density * FLOATING_IME_BOTTOM_INSET
-                            + 0.5f));
+            super(show, false /* hasCallbacks */, types, BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
+                    false /* disable */, 0 /* floatingImeBottomInsets */);
             mFinishCallback = finishCallback;
             mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this);
         }
@@ -497,7 +496,7 @@
                 // the controlled types should be animated regardless of the frame.
                 mAnimationControl = new InsetsAnimationControlImpl(controls,
                         null /* frame */, state, mListener, typesReady, this,
-                        mListener.getDurationMs(), InsetsController.SYSTEM_BARS_INTERPOLATOR,
+                        mListener.getDurationMs(), getInsetsInterpolator(),
                         show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, null /* translator */);
                 SurfaceAnimationThread.getHandler().post(
                         () -> mListener.onReady(mAnimationControl, typesReady));
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index b31c2e4..20216c3 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -167,6 +167,11 @@
         if (aodChanged) {
             // Ensure the new state takes effect.
             mWindowManager.mWindowPlacerLocked.performSurfacePlacement();
+            // If the device can enter AOD and keyguard at the same time, the screen will not be
+            // turned off, so the snapshot needs to be refreshed when these states are changed.
+            if (aodShowing && keyguardShowing && keyguardChanged) {
+                mWindowManager.mTaskSnapshotController.snapshotForSleeping(DEFAULT_DISPLAY);
+            }
         }
 
         if (keyguardChanged) {
diff --git a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
index 4ab5cd6..b1e12b6 100644
--- a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
@@ -77,7 +77,7 @@
             final boolean shouldAttachNavBarToApp =
                     displayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
                             && service.getRecentsAnimationController() == null
-                            && displayContent.getFixedRotationAnimationController() == null;
+                            && displayContent.getFadeRotationAnimationController() == null;
             if (shouldAttachNavBarToApp) {
                 startNavigationBarWindowAnimation(
                         displayContent, durationHint, statusBarTransitionDelay, targets,
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 64ff108..bd84854 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -568,8 +568,9 @@
                             ? mMinimizedHomeBounds
                             : null;
             final Rect contentInsets;
-            if (mTargetActivityRecord != null && mTargetActivityRecord.findMainWindow() != null) {
-                contentInsets = mTargetActivityRecord.findMainWindow()
+            final WindowState targetAppMainWindow = getTargetAppMainWindow();
+            if (targetAppMainWindow != null) {
+                contentInsets = targetAppMainWindow
                         .getInsetsStateWithVisibilityOverride()
                         .calculateInsets(mTargetActivityRecord.getBounds(), Type.systemBars(),
                                 false /* ignoreVisibility */);
@@ -607,8 +608,8 @@
 
     private void attachNavigationBarToApp() {
         if (!mShouldAttachNavBarToAppDuringTransition
-                // Skip the case where the nav bar is controlled by fixed rotation.
-                || mDisplayContent.getFixedRotationAnimationController() != null) {
+                // Skip the case where the nav bar is controlled by fade rotation.
+                || mDisplayContent.getFadeRotationAnimationController() != null) {
             return;
         }
         ActivityRecord topActivity = null;
@@ -1004,9 +1005,7 @@
 
     boolean updateInputConsumerForApp(InputWindowHandle inputWindowHandle) {
         // Update the input consumer touchable region to match the target app main window
-        final WindowState targetAppMainWindow = mTargetActivityRecord != null
-                ? mTargetActivityRecord.findMainWindow()
-                : null;
+        final WindowState targetAppMainWindow = getTargetAppMainWindow();
         if (targetAppMainWindow != null) {
             targetAppMainWindow.getBounds(mTmpRect);
             inputWindowHandle.touchableRegion.set(mTmpRect);
@@ -1026,6 +1025,13 @@
         return mTargetActivityRecord.windowsCanBeWallpaperTarget();
     }
 
+    WindowState getTargetAppMainWindow() {
+        if (mTargetActivityRecord == null) {
+            return null;
+        }
+        return mTargetActivityRecord.findMainWindow();
+    }
+
     boolean isAnimatingTask(Task task) {
         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
             if (task == mPendingAnimations.get(i).mTask) {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 8915eba..5af44317 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -635,20 +635,7 @@
         mHandler.post(() -> {
             try {
                 synchronized (mService.mGlobalLock) {
-                    mTmpTasks.clear();
-                    mService.mRoot.getDisplayContent(displayId).forAllTasks(task -> {
-                        // Since RecentsAnimation will handle task snapshot while switching apps
-                        // with the best capture timing (e.g. IME window capture), No need
-                        // additional task capture while task is controlled by RecentsAnimation.
-                        if (task.isVisible() && !task.isAnimatingByRecents()) {
-                            mTmpTasks.add(task);
-                        }
-                    });
-                    // Allow taking snapshot of home when turning screen off to reduce the delay of
-                    // waking from secure lock to home.
-                    final boolean allowSnapshotHome = displayId == Display.DEFAULT_DISPLAY &&
-                            mService.mPolicy.isKeyguardSecure(mService.mCurrentUserId);
-                    snapshotTasks(mTmpTasks, allowSnapshotHome);
+                    snapshotForSleeping(displayId);
                 }
             } finally {
                 listener.onScreenOff();
@@ -656,6 +643,27 @@
         });
     }
 
+    /** Called when the device is going to sleep (e.g. screen off, AOD without screen off). */
+    void snapshotForSleeping(int displayId) {
+        if (shouldDisableSnapshots()) {
+            return;
+        }
+        mTmpTasks.clear();
+        mService.mRoot.getDisplayContent(displayId).forAllTasks(task -> {
+            // Since RecentsAnimation will handle task snapshot while switching apps with the best
+            // capture timing (e.g. IME window capture), No need additional task capture while task
+            // is controlled by RecentsAnimation.
+            if (task.isVisible() && !task.isAnimatingByRecents()) {
+                mTmpTasks.add(task);
+            }
+        });
+        // Allow taking snapshot of home when turning screen off to reduce the delay of waking from
+        // secure lock to home.
+        final boolean allowSnapshotHome = displayId == Display.DEFAULT_DISPLAY
+                && mService.mPolicy.isKeyguardSecure(mService.mCurrentUserId);
+        snapshotTasks(mTmpTasks, allowSnapshotHome);
+    }
+
     /**
      * @return The {@link Appearance} flags for the top fullscreen opaque window in the given
      *         {@param task}.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 57394d6..6b1071c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -433,11 +433,6 @@
     public static boolean sEnableRemoteKeyguardAnimation =
             SystemProperties.getBoolean(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, false);
 
-    private static final String DISABLE_TRIPLE_BUFFERING_PROPERTY =
-            "ro.sf.disable_triple_buffer";
-
-    static boolean sEnableTripleBuffering = !SystemProperties.getBoolean(
-            DISABLE_TRIPLE_BUFFERING_PROPERTY, false);
 
     /**
      * Allows a fullscreen windowing mode activity to launch in its desired orientation directly
@@ -1747,9 +1742,6 @@
             if (mUseBLAST) {
                 res |= WindowManagerGlobal.ADD_FLAG_USE_BLAST;
             }
-            if (sEnableTripleBuffering) {
-                res |= WindowManagerGlobal.ADD_FLAG_USE_TRIPLE_BUFFERING;
-            }
 
             if (displayContent.mCurrentFocus == null) {
                 displayContent.mWinAddedSinceNullFocus.add(win);
diff --git a/services/core/java/com/android/server/wm/WindowOrientationListener.java b/services/core/java/com/android/server/wm/WindowOrientationListener.java
index 5ef9420..49e704f 100644
--- a/services/core/java/com/android/server/wm/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/wm/WindowOrientationListener.java
@@ -1150,7 +1150,8 @@
                     FrameworkStatsLog.write(
                             FrameworkStatsLog.DEVICE_ROTATED,
                             event.timestamp,
-                            rotationToLogEnum(reportedRotation));
+                            rotationToLogEnum(reportedRotation),
+                            FrameworkStatsLog.DEVICE_ROTATED__ROTATION_EVENT_TYPE__ACTUAL_EVENT);
                 }
             }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6d88387..6da350b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1514,11 +1514,20 @@
     }
 
     void setOrientationChanging(boolean changing) {
-        mOrientationChanging = changing;
         mOrientationChangeTimedOut = false;
+        if (mOrientationChanging == changing) {
+            return;
+        }
+        mOrientationChanging = changing;
         if (changing) {
             mLastFreezeDuration = 0;
-            mWmService.mRoot.mOrientationChangeComplete = false;
+            if (mWmService.mRoot.mOrientationChangeComplete
+                    && mDisplayContent.waitForUnfreeze(this)) {
+                mWmService.mRoot.mOrientationChangeComplete = false;
+            }
+        } else {
+            // The orientation change is completed. If it was hidden by the animation, reshow it.
+            mDisplayContent.finishFadeRotationAnimation(this);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index ebbebbb..0c80f86 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -656,8 +656,10 @@
 
         if (w.getOrientationChanging()) {
             if (!w.isDrawn()) {
-                w.mWmService.mRoot.mOrientationChangeComplete = false;
-                mAnimator.mLastWindowFreezeSource = w;
+                if (w.mDisplayContent.waitForUnfreeze(w)) {
+                    w.mWmService.mRoot.mOrientationChangeComplete = false;
+                    mAnimator.mLastWindowFreezeSource = w;
+                }
                 ProtoLog.v(WM_DEBUG_ORIENTATION,
                         "Orientation continue waiting for draw in %s", w);
             } else {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 5276d9c8..5163a43 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -758,11 +758,15 @@
 
     /** @see WindowState#freezeInsetsState() */
     void setInsetsFrozen(boolean freeze) {
-        if (freeze) {
-            forAllWindows(WindowState::freezeInsetsState, true /* traverseTopToBottom */);
-        } else {
-            forAllWindows(WindowState::clearFrozenInsetsState, true /* traverseTopToBottom */);
-        }
+        forAllWindows(w -> {
+            if (w.mToken == this) {
+                if (freeze) {
+                    w.freezeInsetsState();
+                } else {
+                    w.clearFrozenInsetsState();
+                }
+            }
+        },  true /* traverseTopToBottom */);
     }
 
     @Override
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index f60b354..7f8168a 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -170,13 +170,13 @@
     wrapper->hal()->off();
 }
 
-static void vibratorSetAmplitude(JNIEnv* env, jclass /* clazz */, jlong ptr, jint amplitude) {
+static void vibratorSetAmplitude(JNIEnv* env, jclass /* clazz */, jlong ptr, jfloat amplitude) {
     VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
     if (wrapper == nullptr) {
         ALOGE("vibratorSetAmplitude failed because native wrapper was not initialized");
         return;
     }
-    wrapper->hal()->setAmplitude(static_cast<int32_t>(amplitude));
+    wrapper->hal()->setAmplitude(static_cast<float>(amplitude));
 }
 
 static void vibratorSetExternalControl(JNIEnv* env, jclass /* clazz */, jlong ptr,
@@ -313,9 +313,9 @@
         {"isAvailable", "(J)Z", (void*)vibratorIsAvailable},
         {"on", "(JJJ)V", (void*)vibratorOn},
         {"off", "(J)V", (void*)vibratorOff},
-        {"setAmplitude", "(JI)V", (void*)vibratorSetAmplitude},
+        {"setAmplitude", "(JF)V", (void*)vibratorSetAmplitude},
         {"performEffect", "(JJJJ)J", (void*)vibratorPerformEffect},
-        {"performComposedEffect", "(J[Landroid/os/VibrationEffect$Composition$PrimitiveEffect;J)J",
+        {"performComposedEffect", "(J[Landroid/os/vibrator/PrimitiveSegment;J)J",
          (void*)vibratorPerformComposedEffect},
         {"getSupportedEffects", "(J)[I", (void*)vibratorGetSupportedEffects},
         {"getSupportedPrimitives", "(J)[I", (void*)vibratorGetSupportedPrimitives},
@@ -334,11 +334,10 @@
     jclass listenerClass = FindClassOrDie(env, listenerClassName);
     sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(IJ)V");
 
-    jclass primitiveClass =
-            FindClassOrDie(env, "android/os/VibrationEffect$Composition$PrimitiveEffect");
-    sPrimitiveClassInfo.id = GetFieldIDOrDie(env, primitiveClass, "id", "I");
-    sPrimitiveClassInfo.scale = GetFieldIDOrDie(env, primitiveClass, "scale", "F");
-    sPrimitiveClassInfo.delay = GetFieldIDOrDie(env, primitiveClass, "delay", "I");
+    jclass primitiveClass = FindClassOrDie(env, "android/os/vibrator/PrimitiveSegment");
+    sPrimitiveClassInfo.id = GetFieldIDOrDie(env, primitiveClass, "mPrimitiveId", "I");
+    sPrimitiveClassInfo.scale = GetFieldIDOrDie(env, primitiveClass, "mScale", "F");
+    sPrimitiveClassInfo.delay = GetFieldIDOrDie(env, primitiveClass, "mDelay", "I");
 
     return jniRegisterNativeMethods(env,
                                     "com/android/server/vibrator/VibratorController$NativeWrapper",
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index aed13b2..56e2385 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -139,12 +139,13 @@
     private static final String TAG_ENROLLMENT_SPECIFIC_ID = "enrollment-specific-id";
     private static final String TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS =
             "admin-can-grant-sensors-permissions";
-    private static final String TAG_NETWORK_SLICING_ENABLED = "network-slicing-enabled";
+    private static final String TAG_ENTERPRISE_NETWORK_PREFERENCE_ENABLED =
+            "enterprise-network-preference-enabled";
     private static final String TAG_USB_DATA_SIGNALING = "usb-data-signaling";
     private static final String ATTR_VALUE = "value";
     private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
     private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
-    private static final boolean NETWORK_SLICING_ENABLED_DEFAULT = true;
+    private static final boolean ENTERPRISE_NETWORK_PREFERENCE_ENABLED_DEFAULT = true;
 
     DeviceAdminInfo info;
 
@@ -284,7 +285,8 @@
     public String mOrganizationId;
     public String mEnrollmentSpecificId;
     public boolean mAdminCanGrantSensorsPermissions;
-    public boolean mNetworkSlicingEnabled = NETWORK_SLICING_ENABLED_DEFAULT;
+    public boolean mEnterpriseNetworkPreferenceEnabled =
+            ENTERPRISE_NETWORK_PREFERENCE_ENABLED_DEFAULT;
 
     private static final boolean USB_DATA_SIGNALING_ENABLED_DEFAULT = true;
     boolean mUsbDataSignalingEnabled = USB_DATA_SIGNALING_ENABLED_DEFAULT;
@@ -555,8 +557,9 @@
         }
         writeAttributeValueToXml(out, TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS,
                 mAdminCanGrantSensorsPermissions);
-        if (mNetworkSlicingEnabled != NETWORK_SLICING_ENABLED_DEFAULT) {
-            writeAttributeValueToXml(out, TAG_NETWORK_SLICING_ENABLED, mNetworkSlicingEnabled);
+        if (mEnterpriseNetworkPreferenceEnabled != ENTERPRISE_NETWORK_PREFERENCE_ENABLED_DEFAULT) {
+            writeAttributeValueToXml(out, TAG_ENTERPRISE_NETWORK_PREFERENCE_ENABLED,
+                    mEnterpriseNetworkPreferenceEnabled);
         }
         if (mUsbDataSignalingEnabled != USB_DATA_SIGNALING_ENABLED_DEFAULT) {
             writeAttributeValueToXml(out, TAG_USB_DATA_SIGNALING, mUsbDataSignalingEnabled);
@@ -784,9 +787,9 @@
                 mAlwaysOnVpnPackage = parser.getAttributeValue(null, ATTR_VALUE);
             } else if (TAG_ALWAYS_ON_VPN_LOCKDOWN.equals(tag)) {
                 mAlwaysOnVpnLockdown = parser.getAttributeBoolean(null, ATTR_VALUE, false);
-            } else if (TAG_NETWORK_SLICING_ENABLED.equals(tag)) {
-                mNetworkSlicingEnabled = parser.getAttributeBoolean(
-                        null, ATTR_VALUE, NETWORK_SLICING_ENABLED_DEFAULT);
+            } else if (TAG_ENTERPRISE_NETWORK_PREFERENCE_ENABLED.equals(tag)) {
+                mEnterpriseNetworkPreferenceEnabled = parser.getAttributeBoolean(
+                        null, ATTR_VALUE, ENTERPRISE_NETWORK_PREFERENCE_ENABLED_DEFAULT);
             } else if (TAG_COMMON_CRITERIA_MODE.equals(tag)) {
                 mCommonCriteriaMode = parser.getAttributeBoolean(null, ATTR_VALUE, false);
             } else if (TAG_PASSWORD_COMPLEXITY.equals(tag)) {
@@ -1142,8 +1145,8 @@
         pw.print("mAlwaysOnVpnLockdown=");
         pw.println(mAlwaysOnVpnLockdown);
 
-        pw.print("mNetworkSlicingEnabled=");
-        pw.println(mNetworkSlicingEnabled);
+        pw.print("mEnterpriseNetworkPreferenceEnabled=");
+        pw.println(mEnterpriseNetworkPreferenceEnabled);
 
         pw.print("mCommonCriteriaMode=");
         pw.println(mCommonCriteriaMode);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 283895b..ca56e9b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3092,7 +3092,8 @@
         boolean enableEnterpriseNetworkSlice = true;
         synchronized (getLockObject()) {
             ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
-            enableEnterpriseNetworkSlice = owner != null ? owner.mNetworkSlicingEnabled : true;
+            enableEnterpriseNetworkSlice = owner != null
+                    ? owner.mEnterpriseNetworkPreferenceEnabled : true;
         }
         updateNetworkPreferenceForUser(userId, enableEnterpriseNetworkSlice);
 
@@ -11423,30 +11424,32 @@
     }
 
     @Override
-    public void setNetworkSlicingEnabled(boolean enabled) {
+    public void setEnterpriseNetworkPreferenceEnabled(boolean enabled) {
         if (!mHasFeature) {
             return;
         }
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(isProfileOwner(caller),
-                "Caller is not profile owner; only profile owner may control the network slicing");
+                "Caller is not profile owner;"
+                        + " only profile owner may control the enterprise network preference");
         synchronized (getLockObject()) {
             final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(
                     caller.getUserId());
-            if (requiredAdmin != null && requiredAdmin.mNetworkSlicingEnabled != enabled) {
-                requiredAdmin.mNetworkSlicingEnabled = enabled;
+            if (requiredAdmin != null
+                    && requiredAdmin.mEnterpriseNetworkPreferenceEnabled != enabled) {
+                requiredAdmin.mEnterpriseNetworkPreferenceEnabled = enabled;
                 saveSettingsLocked(caller.getUserId());
             }
         }
         updateNetworkPreferenceForUser(caller.getUserId(), enabled);
         DevicePolicyEventLogger
-                .createEvent(DevicePolicyEnums.SET_NETWORK_SLICING_ENABLED)
+                .createEvent(DevicePolicyEnums.SET_ENTERPRISE_NETWORK_PREFERENCE_ENABLED)
                 .setBoolean(enabled)
                 .write();
     }
 
     @Override
-    public boolean isNetworkSlicingEnabled(int userHandle) {
+    public boolean isEnterpriseNetworkPreferenceEnabled(int userHandle) {
         if (!mHasFeature) {
             return false;
         }
@@ -11457,7 +11460,7 @@
         synchronized (getLockObject()) {
             final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(userHandle);
             if (requiredAdmin != null) {
-                return requiredAdmin.mNetworkSlicingEnabled;
+                return requiredAdmin.mEnterpriseNetworkPreferenceEnabled;
             } else {
                 return false;
             }
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index a88f2b4..932e997 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -377,6 +377,7 @@
 
     dprintf(fd, "Mounts (%d): {\n", int(mMounts.size()));
     for (auto&& [id, ifs] : mMounts) {
+        std::unique_lock ll(ifs->lock);
         const IncFsMount& mnt = *ifs;
         dprintf(fd, "  [%d]: {\n", id);
         if (id != mnt.mountId) {
@@ -422,6 +423,9 @@
 }
 
 bool IncrementalService::needStartDataLoaderLocked(IncFsMount& ifs) {
+    if (!ifs.dataLoaderStub) {
+        return false;
+    }
     if (ifs.dataLoaderStub->isSystemDataLoader()) {
         return true;
     }
@@ -439,6 +443,8 @@
         std::lock_guard l(mLock);
         mounts.reserve(mMounts.size());
         for (auto&& [id, ifs] : mMounts) {
+            std::unique_lock ll(ifs->lock);
+
             if (ifs->mountId != id) {
                 continue;
             }
@@ -456,7 +462,10 @@
     std::thread([this, mounts = std::move(mounts)]() {
         mJni->initializeForCurrentThread();
         for (auto&& ifs : mounts) {
-            ifs->dataLoaderStub->requestStart();
+            std::unique_lock l(ifs->lock);
+            if (ifs->dataLoaderStub) {
+                ifs->dataLoaderStub->requestStart();
+            }
         }
     }).detach();
 }
@@ -671,23 +680,36 @@
         setUidReadTimeouts(storageId, std::move(perUidReadTimeouts));
     }
 
-    // Re-initialize DataLoader.
-    std::unique_lock l(mLock);
-    const auto ifs = getIfsLocked(storageId);
-    if (!ifs) {
-        return false;
-    }
-    if (ifs->dataLoaderStub) {
-        ifs->dataLoaderStub->cleanupResources();
-        ifs->dataLoaderStub = {};
-    }
-    l.unlock();
+    IfsMountPtr ifs;
+    DataLoaderStubPtr dataLoaderStub;
 
-    // DataLoader.
-    auto dataLoaderStub =
-            prepareDataLoader(*ifs, std::move(dataLoaderParams), std::move(statusListener),
-                              healthCheckParams, std::move(healthListener));
-    CHECK(dataLoaderStub);
+    // Re-initialize DataLoader.
+    {
+        ifs = getIfs(storageId);
+        if (!ifs) {
+            return false;
+        }
+
+        std::unique_lock l(ifs->lock);
+        dataLoaderStub = std::exchange(ifs->dataLoaderStub, nullptr);
+    }
+
+    if (dataLoaderStub) {
+        dataLoaderStub->cleanupResources();
+        dataLoaderStub = {};
+    }
+
+    {
+        std::unique_lock l(ifs->lock);
+        if (ifs->dataLoaderStub) {
+            LOG(INFO) << "Skipped data loader stub creation because it already exists";
+            return false;
+        }
+        prepareDataLoaderLocked(*ifs, std::move(dataLoaderParams), std::move(statusListener),
+                                healthCheckParams, std::move(healthListener));
+        CHECK(ifs->dataLoaderStub);
+        dataLoaderStub = ifs->dataLoaderStub;
+    }
 
     if (dataLoaderStub->isSystemDataLoader()) {
         // Readlogs from system dataloader (adb) can always be collected.
@@ -705,13 +727,14 @@
                                          << storageId;
                             return;
                         }
+                        std::unique_lock l(ifs->lock);
                         if (ifs->startLoadingTs != startLoadingTs) {
                             LOG(INFO) << "Can't disable the readlogs, timestamp mismatch (new "
                                          "installation?): "
                                       << storageId;
                             return;
                         }
-                        setStorageParams(*ifs, storageId, /*enableReadLogs=*/false);
+                        disableReadLogsLocked(*ifs);
                     });
     }
 
@@ -733,17 +756,17 @@
 }
 
 void IncrementalService::disallowReadLogs(StorageId storageId) {
-    std::unique_lock l(mLock);
-    const auto ifs = getIfsLocked(storageId);
+    const auto ifs = getIfs(storageId);
     if (!ifs) {
         LOG(ERROR) << "disallowReadLogs failed, invalid storageId: " << storageId;
         return;
     }
+
+    std::unique_lock l(ifs->lock);
     if (!ifs->readLogsAllowed()) {
         return;
     }
     ifs->disallowReadLogs();
-    l.unlock();
 
     const auto metadata = constants().readLogsDisabledMarkerName;
     if (auto err = mIncFs->makeFile(ifs->control,
@@ -755,7 +778,7 @@
         return;
     }
 
-    setStorageParams(storageId, /*enableReadLogs=*/false);
+    disableReadLogsLocked(*ifs);
 }
 
 int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLogs) {
@@ -764,61 +787,66 @@
         LOG(ERROR) << "setStorageParams failed, invalid storageId: " << storageId;
         return -EINVAL;
     }
-    return setStorageParams(*ifs, storageId, enableReadLogs);
-}
 
-int IncrementalService::setStorageParams(IncFsMount& ifs, StorageId storageId,
-                                         bool enableReadLogs) {
-    const auto& params = ifs.dataLoaderStub->params();
-    if (enableReadLogs) {
-        if (!ifs.readLogsAllowed()) {
-            LOG(ERROR) << "setStorageParams failed, readlogs disallowed for storageId: "
-                       << storageId;
-            return -EPERM;
-        }
-
-        // Check loader usage stats permission and apop.
-        if (auto status = mAppOpsManager->checkPermission(kLoaderUsageStats, kOpUsage,
-                                                          params.packageName.c_str());
-            !status.isOk()) {
-            LOG(ERROR) << " Permission: " << kLoaderUsageStats
-                       << " check failed: " << status.toString8();
-            return fromBinderStatus(status);
-        }
-
-        // Check multiuser permission.
-        if (auto status = mAppOpsManager->checkPermission(kInteractAcrossUsers, nullptr,
-                                                          params.packageName.c_str());
-            !status.isOk()) {
-            LOG(ERROR) << " Permission: " << kInteractAcrossUsers
-                       << " check failed: " << status.toString8();
-            return fromBinderStatus(status);
-        }
-
-        // Check installation time.
-        const auto now = mClock->now();
-        const auto startLoadingTs = ifs.startLoadingTs;
-        if (startLoadingTs <= now && now - startLoadingTs > Constants::readLogsMaxInterval) {
-            LOG(ERROR) << "setStorageParams failed, readlogs can't be enabled at this time, "
-                          "storageId: "
-                       << storageId;
-            return -EPERM;
-        }
+    std::unique_lock l(ifs->lock);
+    if (!enableReadLogs) {
+        return disableReadLogsLocked(*ifs);
     }
 
-    if (auto status = applyStorageParams(ifs, enableReadLogs); !status.isOk()) {
-        LOG(ERROR) << "applyStorageParams failed: " << status.toString8();
+    if (!ifs->readLogsAllowed()) {
+        LOG(ERROR) << "enableReadLogs failed, readlogs disallowed for storageId: " << storageId;
+        return -EPERM;
+    }
+
+    if (!ifs->dataLoaderStub) {
+        // This should never happen - only DL can call enableReadLogs.
+        LOG(ERROR) << "enableReadLogs failed: invalid state";
+        return -EPERM;
+    }
+
+    // Check installation time.
+    const auto now = mClock->now();
+    const auto startLoadingTs = ifs->startLoadingTs;
+    if (startLoadingTs <= now && now - startLoadingTs > Constants::readLogsMaxInterval) {
+        LOG(ERROR) << "enableReadLogs failed, readlogs can't be enabled at this time, storageId: "
+                   << storageId;
+        return -EPERM;
+    }
+
+    const auto& packageName = ifs->dataLoaderStub->params().packageName;
+
+    // Check loader usage stats permission and apop.
+    if (auto status =
+                mAppOpsManager->checkPermission(kLoaderUsageStats, kOpUsage, packageName.c_str());
+        !status.isOk()) {
+        LOG(ERROR) << " Permission: " << kLoaderUsageStats
+                   << " check failed: " << status.toString8();
         return fromBinderStatus(status);
     }
 
-    if (enableReadLogs) {
-        registerAppOpsCallback(params.packageName);
+    // Check multiuser permission.
+    if (auto status =
+                mAppOpsManager->checkPermission(kInteractAcrossUsers, nullptr, packageName.c_str());
+        !status.isOk()) {
+        LOG(ERROR) << " Permission: " << kInteractAcrossUsers
+                   << " check failed: " << status.toString8();
+        return fromBinderStatus(status);
     }
 
+    if (auto status = applyStorageParamsLocked(*ifs, /*enableReadLogs=*/true); status != 0) {
+        return status;
+    }
+
+    registerAppOpsCallback(packageName);
+
     return 0;
 }
 
-binder::Status IncrementalService::applyStorageParams(IncFsMount& ifs, bool enableReadLogs) {
+int IncrementalService::disableReadLogsLocked(IncFsMount& ifs) {
+    return applyStorageParamsLocked(ifs, /*enableReadLogs=*/false);
+}
+
+int IncrementalService::applyStorageParamsLocked(IncFsMount& ifs, bool enableReadLogs) {
     os::incremental::IncrementalFileSystemControlParcel control;
     control.cmd.reset(dup(ifs.control.cmd()));
     control.pendingReads.reset(dup(ifs.control.pendingReads()));
@@ -832,8 +860,10 @@
     if (status.isOk()) {
         // Store enabled state.
         ifs.setReadLogsEnabled(enableReadLogs);
+    } else {
+        LOG(ERROR) << "applyStorageParams failed: " << status.toString8();
     }
-    return status;
+    return status.isOk() ? 0 : fromBinderStatus(status);
 }
 
 void IncrementalService::deleteStorage(StorageId storageId) {
@@ -1224,9 +1254,14 @@
         return;
     }
 
-    const auto timeout = std::chrono::duration_cast<milliseconds>(maxPendingTimeUs) -
-            Constants::perUidTimeoutOffset;
-    updateUidReadTimeouts(storage, Clock::now() + timeout);
+    const auto timeout = Clock::now() + maxPendingTimeUs - Constants::perUidTimeoutOffset;
+    addIfsStateCallback(storage, [this, timeout](StorageId storageId, IfsState state) -> bool {
+        if (checkUidReadTimeouts(storageId, state, timeout)) {
+            return true;
+        }
+        clearUidReadTimeouts(storageId);
+        return false;
+    });
 }
 
 void IncrementalService::clearUidReadTimeouts(StorageId storage) {
@@ -1234,39 +1269,32 @@
     if (!ifs) {
         return;
     }
-
     mIncFs->setUidReadTimeouts(ifs->control, {});
 }
 
-void IncrementalService::updateUidReadTimeouts(StorageId storage, Clock::time_point timeLimit) {
-    // Reached maximum timeout.
+bool IncrementalService::checkUidReadTimeouts(StorageId storage, IfsState state,
+                                              Clock::time_point timeLimit) {
     if (Clock::now() >= timeLimit) {
-        return clearUidReadTimeouts(storage);
+        // Reached maximum timeout.
+        return false;
+    }
+    if (state.error) {
+        // Something is wrong, abort.
+        return false;
     }
 
     // Still loading?
-    const auto state = isMountFullyLoaded(storage);
-    if (int(state) < 0) {
-        // Something is wrong, abort.
-        return clearUidReadTimeouts(storage);
-    }
-
-    if (state == incfs::LoadingState::Full) {
-        // Fully loaded, check readLogs collection.
-        const auto ifs = getIfs(storage);
-        if (!ifs->readLogsEnabled()) {
-            return clearUidReadTimeouts(storage);
-        }
+    if (state.fullyLoaded && !state.readLogsEnabled) {
+        return false;
     }
 
     const auto timeLeft = timeLimit - Clock::now();
     if (timeLeft < Constants::progressUpdateInterval) {
         // Don't bother.
-        return clearUidReadTimeouts(storage);
+        return false;
     }
 
-    addTimedJob(*mTimedQueue, storage, Constants::progressUpdateInterval,
-                [this, storage, timeLimit]() { updateUidReadTimeouts(storage, timeLimit); });
+    return true;
 }
 
 std::unordered_set<std::string_view> IncrementalService::adoptMountedInstances() {
@@ -1533,7 +1561,7 @@
         dataLoaderParams.arguments = loader.arguments();
     }
 
-    prepareDataLoader(*ifs, std::move(dataLoaderParams));
+    prepareDataLoaderLocked(*ifs, std::move(dataLoaderParams));
     CHECK(ifs->dataLoaderStub);
 
     std::vector<std::pair<std::string, metadata::BindPoint>> bindPoints;
@@ -1615,24 +1643,10 @@
     }
 }
 
-IncrementalService::DataLoaderStubPtr IncrementalService::prepareDataLoader(
-        IncFsMount& ifs, DataLoaderParamsParcel&& params, DataLoaderStatusListener&& statusListener,
-        const StorageHealthCheckParams& healthCheckParams, StorageHealthListener&& healthListener) {
-    std::unique_lock l(ifs.lock);
-    prepareDataLoaderLocked(ifs, std::move(params), std::move(statusListener), healthCheckParams,
-                            std::move(healthListener));
-    return ifs.dataLoaderStub;
-}
-
 void IncrementalService::prepareDataLoaderLocked(IncFsMount& ifs, DataLoaderParamsParcel&& params,
                                                  DataLoaderStatusListener&& statusListener,
                                                  const StorageHealthCheckParams& healthCheckParams,
                                                  StorageHealthListener&& healthListener) {
-    if (ifs.dataLoaderStub) {
-        LOG(INFO) << "Skipped data loader preparation because it already exists";
-        return;
-    }
-
     FileSystemControlParcel fsControlParcel;
     fsControlParcel.incremental = std::make_optional<IncrementalFileSystemControlParcel>();
     fsControlParcel.incremental->cmd.reset(dup(ifs.control.cmd()));
@@ -1647,6 +1661,29 @@
             new DataLoaderStub(*this, ifs.mountId, std::move(params), std::move(fsControlParcel),
                                std::move(statusListener), healthCheckParams,
                                std::move(healthListener), path::join(ifs.root, constants().mount));
+
+    addIfsStateCallback(ifs.mountId, [this](StorageId storageId, IfsState state) -> bool {
+        if (!state.fullyLoaded || state.readLogsEnabled) {
+            return true;
+        }
+
+        DataLoaderStubPtr dataLoaderStub;
+        {
+            const auto ifs = getIfs(storageId);
+            if (!ifs) {
+                return false;
+            }
+
+            std::unique_lock l(ifs->lock);
+            dataLoaderStub = std::exchange(ifs->dataLoaderStub, nullptr);
+        }
+
+        if (dataLoaderStub) {
+            dataLoaderStub->cleanupResources();
+        }
+
+        return false;
+    });
 }
 
 template <class Duration>
@@ -2070,11 +2107,11 @@
         StorageHealthListener healthListener) {
     DataLoaderStubPtr dataLoaderStub;
     {
-        std::unique_lock l(mLock);
-        const auto& ifs = getIfsLocked(storage);
+        const auto& ifs = getIfs(storage);
         if (!ifs) {
             return false;
         }
+        std::unique_lock l(ifs->lock);
         dataLoaderStub = ifs->dataLoaderStub;
         if (!dataLoaderStub) {
             return false;
@@ -2160,13 +2197,16 @@
         std::lock_guard l(mLock);
         affected.reserve(mMounts.size());
         for (auto&& [id, ifs] : mMounts) {
-            if (ifs->mountId == id && ifs->dataLoaderStub->params().packageName == packageName) {
+            std::unique_lock ll(ifs->lock);
+
+            if (ifs->mountId == id && ifs->dataLoaderStub &&
+                ifs->dataLoaderStub->params().packageName == packageName) {
                 affected.push_back(ifs);
             }
         }
     }
     for (auto&& ifs : affected) {
-        applyStorageParams(*ifs, false);
+        applyStorageParamsLocked(*ifs, /*enableReadLogs=*/false);
     }
 }
 
@@ -2187,6 +2227,101 @@
     return true;
 }
 
+void IncrementalService::addIfsStateCallback(StorageId storageId, IfsStateCallback callback) {
+    bool wasEmpty;
+    {
+        std::lock_guard l(mIfsStateCallbacksLock);
+        wasEmpty = mIfsStateCallbacks.empty();
+        mIfsStateCallbacks[storageId].emplace_back(std::move(callback));
+    }
+    if (wasEmpty) {
+        addTimedJob(*mTimedQueue, kMaxStorageId, Constants::progressUpdateInterval,
+                    [this]() { processIfsStateCallbacks(); });
+    }
+}
+
+void IncrementalService::processIfsStateCallbacks() {
+    StorageId storageId = kInvalidStorageId;
+    std::vector<IfsStateCallback> local;
+    while (true) {
+        {
+            std::lock_guard l(mIfsStateCallbacksLock);
+            if (mIfsStateCallbacks.empty()) {
+                return;
+            }
+            IfsStateCallbacks::iterator it;
+            if (storageId == kInvalidStorageId) {
+                // First entry, initialize the it.
+                it = mIfsStateCallbacks.begin();
+            } else {
+                // Subsequent entries, update the storageId, and shift to the new one.
+                it = mIfsStateCallbacks.find(storageId);
+                if (it == mIfsStateCallbacks.end()) {
+                    // Was removed while processing, too bad.
+                    break;
+                }
+
+                auto& callbacks = it->second;
+                if (callbacks.empty()) {
+                    std::swap(callbacks, local);
+                } else {
+                    callbacks.insert(callbacks.end(), local.begin(), local.end());
+                }
+                if (callbacks.empty()) {
+                    it = mIfsStateCallbacks.erase(it);
+                    if (mIfsStateCallbacks.empty()) {
+                        return;
+                    }
+                } else {
+                    ++it;
+                }
+            }
+
+            if (it == mIfsStateCallbacks.end()) {
+                break;
+            }
+
+            storageId = it->first;
+            auto& callbacks = it->second;
+            if (callbacks.empty()) {
+                // Invalid case, one extra lookup should be ok.
+                continue;
+            }
+            std::swap(callbacks, local);
+        }
+
+        processIfsStateCallbacks(storageId, local);
+    }
+
+    addTimedJob(*mTimedQueue, kMaxStorageId, Constants::progressUpdateInterval,
+                [this]() { processIfsStateCallbacks(); });
+}
+
+void IncrementalService::processIfsStateCallbacks(StorageId storageId,
+                                                  std::vector<IfsStateCallback>& callbacks) {
+    const auto state = isMountFullyLoaded(storageId);
+    IfsState storageState = {};
+    storageState.error = int(state) < 0;
+    storageState.fullyLoaded = state == incfs::LoadingState::Full;
+    if (storageState.fullyLoaded) {
+        const auto ifs = getIfs(storageId);
+        storageState.readLogsEnabled = ifs && ifs->readLogsEnabled();
+    }
+
+    for (auto cur = callbacks.begin(); cur != callbacks.end();) {
+        if ((*cur)(storageId, storageState)) {
+            ++cur;
+        } else {
+            cur = callbacks.erase(cur);
+        }
+    }
+}
+
+void IncrementalService::removeIfsStateCallbacks(StorageId storageId) {
+    std::lock_guard l(mIfsStateCallbacksLock);
+    mIfsStateCallbacks.erase(storageId);
+}
+
 void IncrementalService::getMetrics(StorageId storageId, android::os::PersistableBundle* result) {
     const auto duration = getMillsSinceOldestPendingRead(storageId);
     if (duration >= 0) {
@@ -2197,12 +2332,12 @@
 }
 
 long IncrementalService::getMillsSinceOldestPendingRead(StorageId storageId) {
-    std::unique_lock l(mLock);
-    const auto ifs = getIfsLocked(storageId);
+    const auto ifs = getIfs(storageId);
     if (!ifs) {
         LOG(ERROR) << "getMillsSinceOldestPendingRead failed, invalid storageId: " << storageId;
         return -EINVAL;
     }
+    std::unique_lock l(ifs->lock);
     if (!ifs->dataLoaderStub) {
         LOG(ERROR) << "getMillsSinceOldestPendingRead failed, no data loader: " << storageId;
         return -EINVAL;
@@ -2248,6 +2383,7 @@
         resetHealthControl();
         mService.removeTimedJobs(*mService.mTimedQueue, mId);
     }
+    mService.removeIfsStateCallbacks(mId);
 
     requestDestroy();
 
@@ -2758,7 +2894,7 @@
     mService.mLooper->addFd(
             pendingReadsFd, android::Looper::POLL_CALLBACK, android::Looper::EVENT_INPUT,
             [](int, int, void* data) -> int {
-                auto&& self = (DataLoaderStub*)data;
+                auto self = (DataLoaderStub*)data;
                 self->updateHealthStatus(/*baseline=*/true);
                 return 0;
             },
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index 4a5db06..a8f32de 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -74,6 +74,17 @@
 
 using PerUidReadTimeouts = ::android::os::incremental::PerUidReadTimeouts;
 
+struct IfsState {
+    // If mount is fully loaded.
+    bool fullyLoaded = false;
+    // If read logs are enabled on this mount. Populated only if fullyLoaded == true.
+    bool readLogsEnabled = false;
+    // If there was an error fetching any of the above.
+    bool error = false;
+};
+// Returns true if wants to be called again.
+using IfsStateCallback = std::function<bool(StorageId, IfsState)>;
+
 class IncrementalService final {
 public:
     explicit IncrementalService(ServiceManagerWrapper&& sm, std::string_view rootDir);
@@ -366,7 +377,7 @@
     void setUidReadTimeouts(StorageId storage,
                             std::vector<PerUidReadTimeouts>&& perUidReadTimeouts);
     void clearUidReadTimeouts(StorageId storage);
-    void updateUidReadTimeouts(StorageId storage, Clock::time_point timeLimit);
+    bool checkUidReadTimeouts(StorageId storage, IfsState state, Clock::time_point timeLimit);
 
     std::unordered_set<std::string_view> adoptMountedInstances();
     void mountExistingImages(const std::unordered_set<std::string_view>& mountedRootNames);
@@ -387,11 +398,6 @@
 
     bool needStartDataLoaderLocked(IncFsMount& ifs);
 
-    DataLoaderStubPtr prepareDataLoader(IncFsMount& ifs,
-                                        content::pm::DataLoaderParamsParcel&& params,
-                                        DataLoaderStatusListener&& statusListener = {},
-                                        const StorageHealthCheckParams& healthCheckParams = {},
-                                        StorageHealthListener&& healthListener = {});
     void prepareDataLoaderLocked(IncFsMount& ifs, content::pm::DataLoaderParamsParcel&& params,
                                  DataLoaderStatusListener&& statusListener = {},
                                  const StorageHealthCheckParams& healthCheckParams = {},
@@ -410,8 +416,8 @@
                                              std::string_view path) const;
     int makeDirs(const IncFsMount& ifs, StorageId storageId, std::string_view path, int mode);
 
-    int setStorageParams(IncFsMount& ifs, StorageId storageId, bool enableReadLogs);
-    binder::Status applyStorageParams(IncFsMount& ifs, bool enableReadLogs);
+    int disableReadLogsLocked(IncFsMount& ifs);
+    int applyStorageParamsLocked(IncFsMount& ifs, bool enableReadLogs);
 
     LoadingProgress getLoadingProgressFromPath(const IncFsMount& ifs, std::string_view path) const;
 
@@ -431,6 +437,12 @@
 
     bool addTimedJob(TimedQueueWrapper& timedQueue, MountId id, Milliseconds after, Job what);
     bool removeTimedJobs(TimedQueueWrapper& timedQueue, MountId id);
+
+    void addIfsStateCallback(StorageId storageId, IfsStateCallback callback);
+    void removeIfsStateCallbacks(StorageId storageId);
+    void processIfsStateCallbacks();
+    void processIfsStateCallbacks(StorageId storageId, std::vector<IfsStateCallback>& callbacks);
+
     bool updateLoadingProgress(int32_t storageId,
                                StorageLoadingProgressListener&& progressListener);
     long getMillsSinceOldestPendingRead(StorageId storage);
@@ -456,6 +468,10 @@
     std::mutex mCallbacksLock;
     std::unordered_map<std::string, sp<AppOpsListener>> mCallbackRegistered;
 
+    using IfsStateCallbacks = std::unordered_map<StorageId, std::vector<IfsStateCallback>>;
+    std::mutex mIfsStateCallbacksLock;
+    IfsStateCallbacks mIfsStateCallbacks;
+
     std::atomic_bool mSystemReady = false;
     StorageId mNextId = 0;
 
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 54bc95d..de8822d 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -140,9 +140,7 @@
                             const content::pm::FileSystemControlParcel& control,
                             const sp<content::pm::IDataLoaderStatusListener>& listener) {
         createOkNoStatus(id, params, control, listener);
-        if (mListener) {
-            mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_CREATED);
-        }
+        reportStatus(id);
         return binder::Status::ok();
     }
     binder::Status createOkNoStatus(int32_t id, const content::pm::DataLoaderParamsParcel& params,
@@ -150,33 +148,26 @@
                                     const sp<content::pm::IDataLoaderStatusListener>& listener) {
         mServiceConnector = control.service;
         mListener = listener;
+        mStatus = IDataLoaderStatusListener::DATA_LOADER_CREATED;
         return binder::Status::ok();
     }
     binder::Status startOk(int32_t id) {
-        if (mListener) {
-            mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_STARTED);
-        }
+        setAndReportStatus(id, IDataLoaderStatusListener::DATA_LOADER_STARTED);
         return binder::Status::ok();
     }
     binder::Status stopOk(int32_t id) {
-        if (mListener) {
-            mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_STOPPED);
-        }
+        setAndReportStatus(id, IDataLoaderStatusListener::DATA_LOADER_STOPPED);
         return binder::Status::ok();
     }
     binder::Status destroyOk(int32_t id) {
-        if (mListener) {
-            mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
-        }
+        setAndReportStatus(id, IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
         mListener = nullptr;
         return binder::Status::ok();
     }
     binder::Status prepareImageOk(int32_t id,
                                   const ::std::vector<content::pm::InstallationFileParcel>&,
                                   const ::std::vector<::std::string>&) {
-        if (mListener) {
-            mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_IMAGE_READY);
-        }
+        setAndReportStatus(id, IDataLoaderStatusListener::DATA_LOADER_IMAGE_READY);
         return binder::Status::ok();
     }
     binder::Status storageError(int32_t id) {
@@ -197,10 +188,22 @@
         EXPECT_TRUE(mServiceConnector->setStorageParams(enableReadLogs, &result).isOk());
         return result;
     }
+    int status() const { return mStatus; }
 
 private:
+    void setAndReportStatus(int id, int status) {
+        mStatus = status;
+        reportStatus(id);
+    }
+    void reportStatus(int id) {
+        if (mListener) {
+            mListener->onStatusChanged(id, mStatus);
+        }
+    }
+
     sp<IIncrementalServiceConnector> mServiceConnector;
     sp<IDataLoaderStatusListener> mListener;
+    int mStatus = IDataLoaderStatusListener::DATA_LOADER_DESTROYED;
 };
 
 class MockDataLoaderManager : public DataLoaderManagerWrapper {
@@ -915,7 +918,7 @@
     EXPECT_CALL(*mDataLoader, start(_)).Times(6);
     EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
     EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
-    EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(3);
+    EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(4);
     TemporaryDir tempDir;
     int storageId =
             mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
@@ -1126,7 +1129,7 @@
     EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
     EXPECT_CALL(*mLooper, addFd(MockIncFs::kPendingReadsFd, _, _, _, _)).Times(2);
     EXPECT_CALL(*mLooper, removeFd(MockIncFs::kPendingReadsFd)).Times(2);
-    EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(5);
+    EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(6);
 
     sp<NiceMock<MockStorageHealthListener>> listener{new NiceMock<MockStorageHealthListener>};
     NiceMock<MockStorageHealthListener>* listenerMock = listener.get();
@@ -1314,7 +1317,7 @@
     EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(1);
     // Not expecting callback removal.
     EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
-    EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(1);
+    EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(2);
     TemporaryDir tempDir;
     int storageId =
             mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
@@ -1355,7 +1358,7 @@
     EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(1);
     // Not expecting callback removal.
     EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
-    EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(0);
+    EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(2);
     // System data loader.
     mDataLoaderParcel.packageName = "android";
     TemporaryDir tempDir;
@@ -1366,9 +1369,9 @@
     ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
                                                   {}, {}));
 
-    // No readlogs callback.
-    ASSERT_EQ(mTimedQueue->mAfter, 0ms);
-    ASSERT_EQ(mTimedQueue->mWhat, nullptr);
+    // IfsState callback.
+    auto callback = mTimedQueue->mWhat;
+    mTimedQueue->clearJob(storageId);
 
     ASSERT_GE(mDataLoader->setStorageParams(true), 0);
     // Now advance clock for 1hr.
@@ -1376,6 +1379,8 @@
     ASSERT_GE(mDataLoader->setStorageParams(true), 0);
     // Now advance clock for 2hrs.
     mClock->advance(readLogsMaxInterval);
+    // IfsStorage callback should not affect anything.
+    callback();
     ASSERT_EQ(mDataLoader->setStorageParams(true), 0);
 }
 
@@ -1394,7 +1399,7 @@
     EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(1);
     // Not expecting callback removal.
     EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
-    EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(2);
+    EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(4);
     TemporaryDir tempDir;
     int storageId =
             mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
@@ -1685,6 +1690,144 @@
     mIncrementalService->registerLoadingProgressListener(storageId, listener);
 }
 
+TEST_F(IncrementalServiceTest, testStartDataLoaderUnbindOnAllDone) {
+    mFs->hasFiles();
+
+    const auto stateUpdateInterval = 1s;
+
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
+    // No unbinding just yet.
+    EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
+    EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoader, start(_)).Times(1);
+    EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
+    EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+    // System data loader to get rid of readlog timeout callback.
+    mDataLoaderParcel.packageName = "android";
+    TemporaryDir tempDir;
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
+    ASSERT_GE(storageId, 0);
+    ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+                                                  {}, {}));
+
+    // Started.
+    ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
+
+    // IfsState callback present.
+    ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+    ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
+    auto callback = mTimedQueue->mWhat;
+    mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+
+    // Not loaded yet.
+    EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_))
+            .WillOnce(Return(incfs::LoadingState::MissingBlocks));
+
+    // Send the callback, should not do anything.
+    callback();
+
+    // Still started.
+    ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
+
+    // Still present.
+    ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+    ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
+    callback = mTimedQueue->mWhat;
+    mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+
+    // Fully loaded.
+    EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_)).WillOnce(Return(incfs::LoadingState::Full));
+    // Expect the unbind.
+    EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
+
+    callback();
+
+    // Destroyed.
+    ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
+}
+
+TEST_F(IncrementalServiceTest, testStartDataLoaderUnbindOnAllDoneWithReadlogs) {
+    mFs->hasFiles();
+
+    // Readlogs.
+    mVold->setIncFsMountOptionsSuccess();
+    mAppOpsManager->checkPermissionSuccess();
+
+    const auto stateUpdateInterval = 1s;
+
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
+    // No unbinding just yet.
+    EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
+    EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoader, start(_)).Times(1);
+    EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
+    EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+    // System data loader to get rid of readlog timeout callback.
+    mDataLoaderParcel.packageName = "android";
+    TemporaryDir tempDir;
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
+    ASSERT_GE(storageId, 0);
+    ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+                                                  {}, {}));
+
+    // Started.
+    ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
+
+    // IfsState callback present.
+    ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+    ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
+    auto callback = mTimedQueue->mWhat;
+    mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+
+    // Not loaded yet.
+    EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_))
+            .WillOnce(Return(incfs::LoadingState::MissingBlocks));
+
+    // Send the callback, should not do anything.
+    callback();
+
+    // Still started.
+    ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
+
+    // Still present.
+    ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+    ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
+    callback = mTimedQueue->mWhat;
+    mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+
+    // Fully loaded.
+    EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_))
+            .WillOnce(Return(incfs::LoadingState::Full))
+            .WillOnce(Return(incfs::LoadingState::Full));
+    // But with readlogs.
+    ASSERT_GE(mDataLoader->setStorageParams(true), 0);
+
+    // Send the callback, still nothing.
+    callback();
+
+    // Still started.
+    ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
+
+    // Still present.
+    ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+    ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
+    callback = mTimedQueue->mWhat;
+    mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+
+    // Disable readlogs and expect the unbind.
+    EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
+    ASSERT_GE(mDataLoader->setStorageParams(false), 0);
+
+    callback();
+
+    // Destroyed.
+    ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
+}
+
 TEST_F(IncrementalServiceTest, testRegisterStorageHealthListenerSuccess) {
     mIncFs->openMountSuccess();
     sp<NiceMock<MockStorageHealthListener>> listener{new NiceMock<MockStorageHealthListener>};
@@ -1801,7 +1944,7 @@
     EXPECT_CALL(*mDataLoader, start(_)).Times(1);
     EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
     EXPECT_CALL(*mIncFs, setUidReadTimeouts(_, _)).Times(0);
-    EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(1);
+    EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(2);
     EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
     TemporaryDir tempDir;
     int storageId =
@@ -1829,7 +1972,6 @@
     EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_))
             .WillOnce(Return(incfs::LoadingState::MissingBlocks))
             .WillOnce(Return(incfs::LoadingState::MissingBlocks))
-            .WillOnce(Return(incfs::LoadingState::Full))
             .WillOnce(Return(incfs::LoadingState::Full));
 
     // Mark DataLoader as 'system' so that readlogs don't pollute the timed queue.
@@ -1846,10 +1988,10 @@
 
     {
         // Timed callback present -> 0 progress.
-        ASSERT_EQ(storageId, mTimedQueue->mId);
+        ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
         ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1));
         const auto timedCallback = mTimedQueue->mWhat;
-        mTimedQueue->clearJob(storageId);
+        mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
 
         // Call it again.
         timedCallback();
@@ -1857,10 +1999,10 @@
 
     {
         // Still present -> some progress.
-        ASSERT_EQ(storageId, mTimedQueue->mId);
+        ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
         ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1));
         const auto timedCallback = mTimedQueue->mWhat;
-        mTimedQueue->clearJob(storageId);
+        mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
 
         // Fully loaded but readlogs collection enabled.
         ASSERT_GE(mDataLoader->setStorageParams(true), 0);
@@ -1871,10 +2013,10 @@
 
     {
         // Still present -> fully loaded + readlogs.
-        ASSERT_EQ(storageId, mTimedQueue->mId);
+        ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
         ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1));
         const auto timedCallback = mTimedQueue->mWhat;
-        mTimedQueue->clearJob(storageId);
+        mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
 
         // Now disable readlogs.
         ASSERT_GE(mDataLoader->setStorageParams(false), 0);
diff --git a/services/net/Android.bp b/services/net/Android.bp
index b01e425..800f7ad 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -83,3 +83,15 @@
         "//packages/modules/Connectivity/Tethering"
     ],
 }
+
+filegroup {
+    name: "services-connectivity-shared-srcs",
+    srcs: [
+        // TODO: move to networkstack-client
+        "java/android/net/IpMemoryStore.java",
+        "java/android/net/NetworkMonitorManager.java",
+        // TODO: move to libs/net
+        "java/android/net/util/KeepalivePacketDataUtil.java",
+        "java/android/net/util/NetworkConstants.java",
+    ],
+}
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index e7d0121..eab3b77 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -388,9 +388,11 @@
         private Map<AppPredictionSessionId, SessionInfo> mSessions = new ArrayMap<>();
 
         @Override
-        public void onCreatePredictionSession(AppPredictionContext context,
+        public void onCreatePredictionSession(AppPredictionContext appPredictionContext,
                 AppPredictionSessionId sessionId) {
-            mSessions.put(sessionId, new SessionInfo(context, mDataManager, sessionId.getUserId()));
+            mSessions.put(sessionId,
+                    new SessionInfo(appPredictionContext, mDataManager, sessionId.getUserId(),
+                            getContext()));
         }
 
         @Override
diff --git a/services/people/java/com/android/server/people/SessionInfo.java b/services/people/java/com/android/server/people/SessionInfo.java
index 28612f1..d256d9c 100644
--- a/services/people/java/com/android/server/people/SessionInfo.java
+++ b/services/people/java/com/android/server/people/SessionInfo.java
@@ -20,6 +20,7 @@
 import android.app.prediction.AppPredictionContext;
 import android.app.prediction.AppTarget;
 import android.app.prediction.IPredictionCallback;
+import android.content.Context;
 import android.content.pm.ParceledListSlice;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -40,9 +41,9 @@
             new RemoteCallbackList<>();
 
     SessionInfo(AppPredictionContext predictionContext, DataManager dataManager,
-            @UserIdInt int callingUserId) {
+            @UserIdInt int callingUserId, Context context) {
         mAppTargetPredictor = AppTargetPredictor.create(predictionContext,
-                this::updatePredictions, dataManager, callingUserId);
+                this::updatePredictions, dataManager, callingUserId, context);
     }
 
     void addCallback(IPredictionCallback callback) {
diff --git a/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java b/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java
index c89dadc..e191081 100644
--- a/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java
+++ b/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java
@@ -24,6 +24,7 @@
 import android.app.prediction.AppTarget;
 import android.app.prediction.AppTargetEvent;
 import android.app.prediction.AppTargetId;
+import android.content.Context;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.people.data.DataManager;
@@ -43,10 +44,10 @@
     /** Creates a {@link AppTargetPredictor} instance based on the prediction context. */
     public static AppTargetPredictor create(@NonNull AppPredictionContext predictionContext,
             @NonNull Consumer<List<AppTarget>> updatePredictionsMethod,
-            @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+            @NonNull DataManager dataManager, @UserIdInt int callingUserId, Context context) {
         if (UI_SURFACE_SHARE.equals(predictionContext.getUiSurface())) {
-            return new ShareTargetPredictor(
-                    predictionContext, updatePredictionsMethod, dataManager, callingUserId);
+            return new ShareTargetPredictor(predictionContext, updatePredictionsMethod, dataManager,
+                    callingUserId, context);
         }
         return new AppTargetPredictor(
                 predictionContext, updatePredictionsMethod, dataManager, callingUserId);
@@ -124,6 +125,11 @@
         callback.accept(targets);
     }
 
+    /** To be overridden by the subclass to recycle resources. */
+    @WorkerThread
+    void destroy() {
+    }
+
     AppPredictionContext getPredictionContext() {
         return mPredictionContext;
     }
diff --git a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
index 236ac84..368b737 100644
--- a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
+++ b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
@@ -16,6 +16,8 @@
 
 package com.android.server.people.prediction;
 
+import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
+
 import static java.util.Collections.reverseOrder;
 
 import android.annotation.NonNull;
@@ -23,17 +25,23 @@
 import android.annotation.UserIdInt;
 import android.annotation.WorkerThread;
 import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
+import android.app.prediction.AppPredictor;
 import android.app.prediction.AppTarget;
 import android.app.prediction.AppTargetEvent;
 import android.app.prediction.AppTargetId;
+import android.content.Context;
 import android.content.IntentFilter;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutManager.ShareShortcutInfo;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
 import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ChooserActivity;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.server.people.data.ConversationInfo;
 import com.android.server.people.data.DataManager;
 import com.android.server.people.data.EventHistory;
@@ -52,14 +60,27 @@
 
     private static final String TAG = "ShareTargetPredictor";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final String REMOTE_APP_PREDICTOR_KEY = "remote_app_predictor";
     private final IntentFilter mIntentFilter;
+    private final AppPredictor mRemoteAppPredictor;
 
     ShareTargetPredictor(@NonNull AppPredictionContext predictionContext,
             @NonNull Consumer<List<AppTarget>> updatePredictionsMethod,
-            @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+            @NonNull DataManager dataManager,
+            @UserIdInt int callingUserId, @NonNull Context context) {
         super(predictionContext, updatePredictionsMethod, dataManager, callingUserId);
         mIntentFilter = predictionContext.getExtras().getParcelable(
                 ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY);
+        if (DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED,
+                false)) {
+            predictionContext.getExtras().putBoolean(REMOTE_APP_PREDICTOR_KEY, true);
+            mRemoteAppPredictor = context.createContextAsUser(UserHandle.of(callingUserId), 0)
+                    .getSystemService(AppPredictionManager.class)
+                    .createAppPredictionSession(predictionContext);
+        } else {
+            mRemoteAppPredictor = null;
+        }
     }
 
     /** Reports chosen history of direct/app share targets. */
@@ -72,6 +93,9 @@
         if (mIntentFilter != null) {
             getDataManager().reportShareTargetEvent(event, mIntentFilter);
         }
+        if (mRemoteAppPredictor != null) {
+            mRemoteAppPredictor.notifyAppTargetEvent(event);
+        }
     }
 
     /** Provides prediction on direct share targets */
@@ -129,6 +153,15 @@
         callback.accept(appTargetList);
     }
 
+    /** Recycles resources. */
+    @WorkerThread
+    @Override
+    void destroy() {
+        if (mRemoteAppPredictor != null) {
+            mRemoteAppPredictor.destroy();
+        }
+    }
+
     private List<ShareTarget> getDirectShareTargets() {
         List<ShareTarget> shareTargets = new ArrayList<>();
         List<ShareShortcutInfo> shareShortcuts =
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
index d5eda20..dce853a 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
@@ -41,7 +41,12 @@
     }
 
     private val platformCompat: PlatformCompat = mockThrowOnUnmocked {
-        whenever(isChangeEnabled(eq(DomainVerificationCollector.RESTRICT_DOMAINS), any())) {
+        whenever(
+            isChangeEnabledInternalNoLogging(
+                eq(DomainVerificationCollector.RESTRICT_DOMAINS),
+                any()
+            )
+        ) {
             (arguments[1] as ApplicationInfo).targetSdkVersion >= Build.VERSION_CODES.S
         }
     }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index 7e25901..1b0a305 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -114,12 +114,7 @@
                     it,
                     mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } },
                     mockThrowOnUnmocked {
-                        whenever(
-                            isChangeEnabled(
-                                anyLong(),
-                                any()
-                            )
-                        ) { true }
+                        whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
                     }).apply {
                     setConnection(connection)
                 }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
index 0e74b65..ef79b08 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
@@ -310,7 +310,7 @@
             }, mockThrowOnUnmocked {
                 whenever(linkedApps) { ArraySet<String>() }
             }, mockThrowOnUnmocked {
-                whenever(isChangeEnabled(anyLong(), any())) { true }
+                whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
             }).apply {
                 setConnection(mockThrowOnUnmocked {
                     whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
index fe3672d..0ce16e6 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
@@ -43,6 +43,7 @@
 import org.mockito.ArgumentMatchers
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
 import org.mockito.ArgumentMatchers.anyString
 import java.util.UUID
 
@@ -366,7 +367,7 @@
             }, mockThrowOnUnmocked {
                 whenever(linkedApps) { ArraySet<String>() }
             }, mockThrowOnUnmocked {
-                whenever(isChangeEnabled(ArgumentMatchers.anyLong(), any())) { true }
+                whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
             }).apply {
                 setConnection(mockThrowOnUnmocked {
                     whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
index 377bae1..b7c6922 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
@@ -98,7 +98,7 @@
                         context,
                         mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } },
                         mockThrowOnUnmocked {
-                            whenever(isChangeEnabled(anyLong(),any())) { true }
+                            whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
                         }).apply {
                         setConnection(connection)
                     }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
index 44c1b8f..54648ab 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
@@ -71,7 +71,7 @@
         }, mockThrowOnUnmocked {
             whenever(linkedApps) { ArraySet<String>() }
         }, mockThrowOnUnmocked {
-            whenever(isChangeEnabled(anyLong(), any())) { true }
+            whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
         }).apply {
             setConnection(mockThrowOnUnmocked {
                 whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 7925b69..0fcda81 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -660,15 +660,19 @@
                 new JobInfo.Builder(101, new ComponentName("foo", "bar")).build());
 
         markImplicitConstraintsSatisfied(job, false);
-        job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+        job.setBackgroundNotRestrictedConstraintSatisfied(
+                sElapsedRealtimeClock.millis(), false, false);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
-        job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+        job.setBackgroundNotRestrictedConstraintSatisfied(
+                sElapsedRealtimeClock.millis(), true, false);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
 
         markImplicitConstraintsSatisfied(job, true);
-        job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+        job.setBackgroundNotRestrictedConstraintSatisfied(
+                sElapsedRealtimeClock.millis(), false, false);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
-        job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+        job.setBackgroundNotRestrictedConstraintSatisfied(
+                sElapsedRealtimeClock.millis(), true, false);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
     }
 
@@ -677,7 +681,7 @@
         job.setDeviceNotDozingConstraintSatisfied(
                 sElapsedRealtimeClock.millis(), isSatisfied, false);
         job.setBackgroundNotRestrictedConstraintSatisfied(
-                sElapsedRealtimeClock.millis(), isSatisfied);
+                sElapsedRealtimeClock.millis(), isSatisfied, false);
     }
 
     private static JobStatus createJobStatus(long earliestRunTimeElapsedMillis,
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 f73af53..ee1a4f4 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
@@ -384,7 +384,8 @@
         // Make sure Doze and background-not-restricted don't affect tests.
         js.setDeviceNotDozingConstraintSatisfied(/* nowElapsed */ sElapsedRealtimeClock.millis(),
                 /* state */ true, /* allowlisted */false);
-        js.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+        js.setBackgroundNotRestrictedConstraintSatisfied(
+                sElapsedRealtimeClock.millis(), true, false);
         return js;
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java
index 0597443..ef48646 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java
@@ -19,8 +19,6 @@
 import android.os.IPowerManager;
 import android.os.PowerManager.LocationPowerSaveMode;
 
-import com.android.server.location.eventlog.LocationEventLog;
-
 /**
  * Version of LocationPowerSaveModeHelper for testing. Power save mode is initialized as "no
  * change".
@@ -30,8 +28,7 @@
     @LocationPowerSaveMode
     private int mLocationPowerSaveMode;
 
-    public FakeLocationPowerSaveModeHelper(LocationEventLog locationEventLog) {
-        super(locationEventLog);
+    public FakeLocationPowerSaveModeHelper() {
         mLocationPowerSaveMode = IPowerManager.LOCATION_MODE_NO_CHANGE;
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java
index 6156ba9..28da027 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java
@@ -43,7 +43,6 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.LocalServices;
-import com.android.server.location.eventlog.LocationEventLog;
 import com.android.server.location.injector.LocationPowerSaveModeHelper.LocationPowerSaveModeChangedListener;
 
 import org.junit.After;
@@ -85,7 +84,7 @@
         Context context = mock(Context.class);
         doReturn(powerManager).when(context).getSystemService(PowerManager.class);
 
-        mHelper = new SystemLocationPowerSaveModeHelper(context, new LocationEventLog());
+        mHelper = new SystemLocationPowerSaveModeHelper(context);
         mHelper.onSystemReady();
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
index 1f102ac..ae70dad 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
@@ -16,8 +16,6 @@
 
 package com.android.server.location.injector;
 
-import com.android.server.location.eventlog.LocationEventLog;
-
 public class TestInjector implements Injector {
 
     private final FakeUserInfoHelper mUserInfoHelper;
@@ -35,17 +33,13 @@
     private final LocationUsageLogger mLocationUsageLogger;
 
     public TestInjector() {
-        this(new LocationEventLog());
-    }
-
-    public TestInjector(LocationEventLog eventLog) {
         mUserInfoHelper = new FakeUserInfoHelper();
         mAlarmHelper = new FakeAlarmHelper();
         mAppOpsHelper = new FakeAppOpsHelper();
         mLocationPermissionsHelper = new FakeLocationPermissionsHelper(mAppOpsHelper);
         mSettingsHelper = new FakeSettingsHelper();
         mAppForegroundHelper = new FakeAppForegroundHelper();
-        mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper(eventLog);
+        mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper();
         mScreenInteractiveHelper = new FakeScreenInteractiveHelper();
         mDeviceStationaryHelper = new FakeDeviceStationaryHelper();
         mDeviceIdleHelper = new FakeDeviceIdleHelper();
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 1b58e92..24b85f0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -83,7 +83,6 @@
 
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
-import com.android.server.location.eventlog.LocationEventLog;
 import com.android.server.location.injector.FakeUserInfoHelper;
 import com.android.server.location.injector.TestInjector;
 
@@ -161,19 +160,17 @@
         doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
         doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
 
-        LocationEventLog eventLog = new LocationEventLog();
-
-        mInjector = new TestInjector(eventLog);
+        mInjector = new TestInjector();
         mInjector.getUserInfoHelper().startUser(OTHER_USER);
 
-        mPassive = new PassiveLocationProviderManager(mContext, mInjector, eventLog);
+        mPassive = new PassiveLocationProviderManager(mContext, mInjector);
         mPassive.startManager();
         mPassive.setRealProvider(new PassiveLocationProvider(mContext));
 
         mProvider = new TestProvider(PROPERTIES, PROVIDER_IDENTITY);
         mProvider.setProviderAllowed(true);
 
-        mManager = new LocationProviderManager(mContext, mInjector, eventLog, NAME, mPassive);
+        mManager = new LocationProviderManager(mContext, mInjector, NAME, mPassive);
         mManager.startManager();
         mManager.setRealProvider(mProvider);
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
index c3cca64..04e0151 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
@@ -36,7 +36,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.server.location.eventlog.LocationEventLog;
 import com.android.server.location.injector.TestInjector;
 import com.android.server.location.test.FakeProvider;
 
@@ -77,7 +76,7 @@
         mDelegateProvider = new FakeProvider(mDelegate);
 
         mProvider = new StationaryThrottlingLocationProvider("test_provider", mInjector,
-                mDelegateProvider, new LocationEventLog());
+                mDelegateProvider);
         mProvider.getController().setListener(mListener);
         mProvider.getController().start();
     }
diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
index 1c96838..1b8ab21 100644
--- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
@@ -18,6 +18,7 @@
 
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.AdditionalAnswers.returnsArgAt;
 import static org.mockito.ArgumentMatchers.any;
@@ -67,6 +68,7 @@
     private static final String PACKAGE_SCHEME = "package";
     private static final String PACKAGE_NAME_1 = "package1";
     private static final String PACKAGE_NAME_2 = "package2";
+    private static final String PACKAGE_NAME_3 = "package3";
     private static final int USER_ID_1 = 1;
     private static final int USER_ID_2 = 2;
 
@@ -107,6 +109,8 @@
 
         List<PackageInfo> packages = new ArrayList<>();
         packages.add(makePackageInfo(PACKAGE_NAME_1));
+        packages.add(makePackageInfo(PACKAGE_NAME_2));
+        packages.add(makePackageInfo(PACKAGE_NAME_3));
         doReturn(new ParceledListSlice<>(packages)).when(mIPackageManager).getInstalledPackages(
                 intThat(arg -> (arg & MATCH_ANY_USER) != 0), anyInt());
         mAppHibernationService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
@@ -179,6 +183,26 @@
         assertTrue(mAppHibernationService.isHibernatingGlobally(PACKAGE_NAME_1));
     }
 
+    @Test
+    public void testGetHibernatingPackagesForUser_returnsCorrectPackages() throws RemoteException {
+        // GIVEN an unlocked user with all packages installed
+        UserInfo userInfo =
+                addUser(USER_ID_2, new String[]{PACKAGE_NAME_1, PACKAGE_NAME_2, PACKAGE_NAME_3});
+        mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo));
+        doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2);
+
+        // WHEN packages are hibernated for the user
+        mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true);
+        mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_2, USER_ID_2, true);
+
+        // THEN the hibernating packages returned matches
+        List<String> hibernatingPackages =
+                mAppHibernationService.getHibernatingPackagesForUser(USER_ID_2);
+        assertEquals(2, hibernatingPackages.size());
+        assertTrue(hibernatingPackages.contains(PACKAGE_NAME_1));
+        assertTrue(hibernatingPackages.contains(PACKAGE_NAME_2));
+    }
+
     /**
      * Add a mock user with one package.
      */
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
new file mode 100644
index 0000000..6cdac1a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
@@ -0,0 +1,220 @@
+/*
+ * 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 org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.IBiometricService;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+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.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@SmallTest
+public class UserAwareBiometricSchedulerTest {
+
+    private static final String TAG = "BiometricSchedulerTest";
+    private static final int TEST_SENSOR_ID = 0;
+
+    private UserAwareBiometricScheduler mScheduler;
+    private IBinder mToken;
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private IBiometricService mBiometricService;
+
+    private TestUserStartedCallback mUserStartedCallback;
+    private TestUserStoppedCallback mUserStoppedCallback;
+    private int mCurrentUserId = UserHandle.USER_NULL;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mToken = new Binder();
+        mUserStartedCallback = new TestUserStartedCallback();
+        mUserStoppedCallback = new TestUserStoppedCallback();
+
+        mScheduler = new UserAwareBiometricScheduler(TAG,
+                null /* gestureAvailabilityDispatcher */,
+                mBiometricService,
+                () -> mCurrentUserId,
+                new UserAwareBiometricScheduler.UserSwitchCallback() {
+                    @NonNull
+                    @Override
+                    public StopUserClient<?> getStopUserClient(int userId) {
+                        return new TestStopUserClient(mContext, Object::new, mToken, userId,
+                                TEST_SENSOR_ID, mUserStoppedCallback);
+                    }
+
+                    @NonNull
+                    @Override
+                    public StartUserClient<?> getStartUserClient(int newUserId) {
+                        return new TestStartUserClient(mContext, Object::new, mToken, newUserId,
+                                TEST_SENSOR_ID, mUserStartedCallback);
+                    }
+                });
+    }
+
+    @Test
+    public void testScheduleOperation_whenNoUser() {
+        mCurrentUserId = UserHandle.USER_NULL;
+
+        final int nextUserId = 0;
+
+        BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+        when(nextClient.getTargetUserId()).thenReturn(nextUserId);
+
+        mScheduler.scheduleClientMonitor(nextClient);
+        verify(nextClient, never()).start(any());
+        assertEquals(0, mUserStoppedCallback.numInvocations);
+        assertEquals(1, mUserStartedCallback.numInvocations);
+
+        waitForIdle();
+        verify(nextClient).start(any());
+    }
+
+    @Test
+    public void testScheduleOperation_whenSameUser() {
+        mCurrentUserId = 10;
+
+        BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+        when(nextClient.getTargetUserId()).thenReturn(mCurrentUserId);
+
+        mScheduler.scheduleClientMonitor(nextClient);
+
+        waitForIdle();
+
+        verify(nextClient).start(any());
+        assertEquals(0, mUserStoppedCallback.numInvocations);
+        assertEquals(0, mUserStartedCallback.numInvocations);
+    }
+
+    @Test
+    public void testScheduleOperation_whenDifferentUser() {
+        mCurrentUserId = 10;
+
+        final int nextUserId = 11;
+        BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+        when(nextClient.getTargetUserId()).thenReturn(nextUserId);
+
+        mScheduler.scheduleClientMonitor(nextClient);
+
+        waitForIdle();
+        assertEquals(1, mUserStoppedCallback.numInvocations);
+
+        waitForIdle();
+        assertEquals(1, mUserStartedCallback.numInvocations);
+
+        waitForIdle();
+        verify(nextClient).start(any());
+    }
+
+    private static void waitForIdle() {
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
+    private class TestUserStoppedCallback implements StopUserClient.UserStoppedCallback {
+
+        int numInvocations;
+
+        @Override
+        public void onUserStopped() {
+            numInvocations++;
+            mCurrentUserId = UserHandle.USER_NULL;
+        }
+    }
+
+    private class TestUserStartedCallback implements StartUserClient.UserStartedCallback {
+
+        int numInvocations;
+
+        @Override
+        public void onUserStarted(int newUserId) {
+            numInvocations++;
+            mCurrentUserId = newUserId;
+        }
+    }
+
+    private static class TestStopUserClient extends StopUserClient<Object> {
+        public TestStopUserClient(@NonNull Context context,
+                @NonNull LazyDaemon<Object> lazyDaemon, @Nullable IBinder token, int userId,
+                int sensorId, @NonNull UserStoppedCallback callback) {
+            super(context, lazyDaemon, token, userId, sensorId, callback);
+        }
+
+        @Override
+        protected void startHalOperation() {
+
+        }
+
+        @Override
+        public void start(@NonNull Callback callback) {
+            super.start(callback);
+            mUserStoppedCallback.onUserStopped();
+            callback.onClientFinished(this, true /* success */);
+        }
+
+        @Override
+        public void unableToStart() {
+
+        }
+    }
+
+    private static class TestStartUserClient extends StartUserClient<Object> {
+        public TestStartUserClient(@NonNull Context context,
+                @NonNull LazyDaemon<Object> lazyDaemon, @Nullable IBinder token, int userId,
+                int sensorId, @NonNull UserStartedCallback callback) {
+            super(context, lazyDaemon, token, userId, sensorId, callback);
+        }
+
+        @Override
+        protected void startHalOperation() {
+
+        }
+
+        @Override
+        public void start(@NonNull Callback callback) {
+            super.start(callback);
+            mUserStartedCallback.onUserStarted(getTargetUserId());
+            callback.onClientFinished(this, true /* success */);
+        }
+
+        @Override
+        public void unableToStart() {
+
+        }
+    }
+}
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 576f9c2..557d07c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -4037,16 +4037,16 @@
     }
 
     @Test
-    public void testGetSetNetworkSlicing() throws Exception {
+    public void testGetSetEnterpriseNetworkPreference() throws Exception {
         assertExpectException(SecurityException.class, null,
-                () -> dpm.setNetworkSlicingEnabled(false));
+                () -> dpm.setEnterpriseNetworkPreferenceEnabled(false));
 
         assertExpectException(SecurityException.class, null,
-                () -> dpm.isNetworkSlicingEnabled());
+                () -> dpm.isEnterpriseNetworkPreferenceEnabled());
 
         setupProfileOwner();
-        dpm.setNetworkSlicingEnabled(false);
-        assertThat(dpm.isNetworkSlicingEnabled()).isFalse();
+        dpm.setEnterpriseNetworkPreferenceEnabled(false);
+        assertThat(dpm.isEnterpriseNetworkPreferenceEnabled()).isFalse();
         // TODO(b/178655595)
         // verify(getServices().connectivityManager, times(1)).setNetworkPreferenceForUser(
         //         any(UserHandle.class),
@@ -4055,8 +4055,8 @@
         //         any(Runnable.class)
         //);
 
-        dpm.setNetworkSlicingEnabled(true);
-        assertThat(dpm.isNetworkSlicingEnabled()).isTrue();
+        dpm.setEnterpriseNetworkPreferenceEnabled(true);
+        assertThat(dpm.isEnterpriseNetworkPreferenceEnabled()).isTrue();
         // TODO(b/178655595)
         // verify(getServices().connectivityManager, times(1)).setNetworkPreferenceForUser(
         //         any(UserHandle.class),
diff --git a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
index a7b32ac..68a6e60 100644
--- a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
@@ -96,7 +96,7 @@
                     case ACTION_JOB_STOPPED:
                         mTestJobStatus.running = false;
                         mTestJobStatus.jobId = params.getJobId();
-                        mTestJobStatus.stopReason = params.getStopReason();
+                        mTestJobStatus.stopReason = params.getLegacyStopReason();
                         break;
                 }
             }
diff --git a/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java b/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java
index 3ea86f2..87881bf 100644
--- a/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java
+++ b/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java
@@ -47,7 +47,7 @@
         int reason = params.getStopReason();
         int event = TestEnvironment.EVENT_STOP_JOB;
         Log.d(TAG, "stop reason: " + String.valueOf(reason));
-        if (reason == JobParameters.REASON_PREEMPT) {
+        if (reason == JobParameters.STOP_REASON_PREEMPT) {
             event = TestEnvironment.EVENT_PREEMPT_JOB;
             Log.d(TAG, "preempted " + String.valueOf(params.getJobId()));
         }
diff --git a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
index ecff409..5066240 100644
--- a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
@@ -47,12 +47,14 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
+import android.provider.DeviceConfig;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
 import android.testing.TestableLooper;
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.server.LocalServices;
 
 import org.junit.After;
@@ -126,6 +128,10 @@
                 .setPredictedTargetCount(APP_PREDICTION_TARGET_COUNT)
                 .setExtras(new Bundle())
                 .build();
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED,
+                Boolean.toString(false),
+                true /* makeDefault*/);
     }
 
     @After
diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
index b09a3c3..fac5c1f 100644
--- a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
@@ -22,13 +22,17 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anySet;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
 import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
 import android.app.prediction.AppTargetId;
 import android.content.ComponentName;
 import android.content.Context;
@@ -38,9 +42,11 @@
 import android.content.pm.ShortcutManager.ShareShortcutInfo;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.provider.DeviceConfig;
 import android.util.Range;
 
 import com.android.internal.app.ChooserActivity;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.server.people.data.ConversationInfo;
 import com.android.server.people.data.DataManager;
 import com.android.server.people.data.EventHistory;
@@ -71,6 +77,14 @@
     private static final String PACKAGE_3 = "pkg3";
     private static final String CLASS_1 = "cls1";
     private static final String CLASS_2 = "cls2";
+    private static final AppTargetEvent APP_TARGET_EVENT =
+            new AppTargetEvent.Builder(
+                    new AppTarget.Builder(
+                        new AppTargetId("cls1#pkg1"), PACKAGE_1, UserHandle.of(USER_ID)).build(),
+                        AppTargetEvent.ACTION_LAUNCH)
+                    .setLaunchLocation(ChooserActivity.LAUNCH_LOCATION_DIRECT_SHARE)
+                    .build();
+    private static final IntentFilter INTENT_FILTER = IntentFilter.create("SEND", "text/plain");
 
     @Mock private Context mContext;
     @Mock private DataManager mDataManager;
@@ -102,17 +116,33 @@
         when(mDataManager.getShareShortcuts(any(), anyInt())).thenReturn(mShareShortcuts);
         when(mDataManager.getPackage(PACKAGE_1, USER_ID)).thenReturn(mPackageData1);
         when(mDataManager.getPackage(PACKAGE_2, USER_ID)).thenReturn(mPackageData2);
+        when(mContext.createContextAsUser(any(), any())).thenReturn(mContext);
+        when(mContext.getSystemServiceName(AppPredictionManager.class)).thenReturn(
+                Context.APP_PREDICTION_SERVICE);
+        when(mContext.getSystemService(AppPredictionManager.class))
+                .thenReturn(new AppPredictionManager(mContext));
 
         Bundle bundle = new Bundle();
-        bundle.putObject(ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY,
-                IntentFilter.create("SEND", "text/plain"));
+        bundle.putObject(ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY, INTENT_FILTER);
         AppPredictionContext predictionContext = new AppPredictionContext.Builder(mContext)
                 .setUiSurface(UI_SURFACE_SHARE)
                 .setPredictedTargetCount(NUM_PREDICTED_TARGETS)
                 .setExtras(bundle)
                 .build();
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED,
+                Boolean.toString(true),
+                true /* makeDefault*/);
         mPredictor = new ShareTargetPredictor(
-                predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID);
+                predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID, mContext);
+    }
+
+    @Test
+    public void testReportAppTargetEvent() {
+        mPredictor.reportAppTargetEvent(APP_TARGET_EVENT);
+
+        verify(mDataManager, times(1))
+                .reportShareTargetEvent(eq(APP_TARGET_EVENT), eq(INTENT_FILTER));
     }
 
     @Test
@@ -240,7 +270,7 @@
                 .setExtras(new Bundle())
                 .build();
         mPredictor = new ShareTargetPredictor(
-                predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID);
+                predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID, mContext);
 
         mPredictor.predictTargets();
 
@@ -349,7 +379,7 @@
                 .setExtras(new Bundle())
                 .build();
         mPredictor = new ShareTargetPredictor(
-                predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID);
+                predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID, mContext);
         AppTarget appTarget1 = new AppTarget.Builder(
                 new AppTargetId("cls1#pkg1"), PACKAGE_1, UserHandle.of(USER_ID))
                 .build();
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 029e9a3..1ab70e5 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -48,10 +48,12 @@
 import android.app.appsearch.AppSearchBatchResult;
 import android.app.appsearch.AppSearchManager;
 import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.GenericDocument;
 import android.app.appsearch.IAppSearchBatchResultCallback;
 import android.app.appsearch.IAppSearchManager;
 import android.app.appsearch.IAppSearchResultCallback;
 import android.app.appsearch.PackageIdentifier;
+import android.app.appsearch.SearchResultPage;
 import android.app.role.OnRoleHoldersChangedListener;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ActivityNotFoundException;
@@ -159,7 +161,7 @@
                 case Context.DEVICE_POLICY_SERVICE:
                     return mMockDevicePolicyManager;
                 case Context.APP_SEARCH_SERVICE:
-                    return new AppSearchManager(getTestContext(), mMockAppSearchManager);
+                    return new AppSearchManager(this, mMockAppSearchManager);
                 case Context.ROLE_SERVICE:
                     // RoleManager is final and cannot be mocked, so we only override the inject
                     // accessor methods in ShortcutService.
@@ -189,6 +191,12 @@
         }
 
         @Override
+        public Context createContextAsUser(UserHandle user, int flags) {
+            when(mMockPackageManager.getUserId()).thenReturn(user.getIdentifier());
+            return this;
+        }
+
+        @Override
         public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
                 IntentFilter filter, String broadcastPermission, Handler scheduler) {
             // ignore.
@@ -196,12 +204,6 @@
         }
 
         @Override
-        public Context createContextAsUser(UserHandle user, int flags) {
-            when(mMockPackageManager.getUserId()).thenReturn(user.getIdentifier());
-            return this;
-        }
-
-        @Override
         public void unregisterReceiver(BroadcastReceiver receiver) {
             // ignore.
         }
@@ -238,6 +240,15 @@
         }
 
         @Override
+        public Context createContextAsUser(UserHandle user, int flags) {
+            super.createContextAsUser(user, flags);
+            final ServiceContext ctx = spy(new ServiceContext());
+            when(ctx.getUser()).thenReturn(user);
+            when(ctx.getUserId()).thenReturn(user.getIdentifier());
+            return ctx;
+        }
+
+        @Override
         public int getUserId() {
             return UserHandle.USER_SYSTEM;
         }
@@ -620,6 +631,11 @@
 
         protected Map<String, List<PackageIdentifier>> mSchemasPackageAccessible =
                 new ArrayMap<>(1);
+        private Map<String, Map<String, GenericDocument>> mDocumentMap = new ArrayMap<>(1);
+
+        private String getKey(int userId, String databaseName) {
+            return new StringBuilder().append(userId).append("@").append(databaseName).toString();
+        }
 
         @Override
         public void setSchema(String packageName, String databaseName, List<Bundle> schemaBundles,
@@ -653,21 +669,77 @@
         public void putDocuments(String packageName, String databaseName,
                 List<Bundle> documentBundles, int userId, IAppSearchBatchResultCallback callback)
                 throws RemoteException {
-            ignore(callback);
+            final List<GenericDocument> docs = new ArrayList<>(documentBundles.size());
+            for (Bundle bundle : documentBundles) {
+                docs.add(new GenericDocument(bundle));
+            }
+            final AppSearchBatchResult.Builder<String, Void> builder =
+                    new AppSearchBatchResult.Builder<>();
+            final String key = getKey(userId, databaseName);
+            Map<String, GenericDocument> docMap = mDocumentMap.get(key);
+            for (GenericDocument doc : docs) {
+                builder.setSuccess(doc.getUri(), null);
+                if (docMap == null) {
+                    docMap = new ArrayMap<>(1);
+                    mDocumentMap.put(key, docMap);
+                }
+                docMap.put(doc.getUri(), doc);
+            }
+            callback.onResult(builder.build());
         }
 
         @Override
         public void getDocuments(String packageName, String databaseName, String namespace,
                 List<String> uris, Map<String, List<String>> typePropertyPaths, int userId,
                 IAppSearchBatchResultCallback callback) throws RemoteException {
-            ignore(callback);
+            final AppSearchBatchResult.Builder<String, Bundle> builder =
+                    new AppSearchBatchResult.Builder<>();
+            final String key = getKey(userId, databaseName);
+            if (!mDocumentMap.containsKey(key)) {
+                for (String uri : uris) {
+                    builder.setFailure(uri, AppSearchResult.RESULT_NOT_FOUND,
+                            key + " not found when getting: " + uri);
+                }
+            } else {
+                final Map<String, GenericDocument> docs = mDocumentMap.get(key);
+                for (String uri : uris) {
+                    if (docs.containsKey(uri)) {
+                        builder.setSuccess(uri, docs.get(uri).getBundle());
+                    } else {
+                        builder.setFailure(uri, AppSearchResult.RESULT_NOT_FOUND,
+                                "shortcut not found: " + uri);
+                    }
+                }
+            }
+            callback.onResult(builder.build());
         }
 
         @Override
         public void query(String packageName, String databaseName, String queryExpression,
                 Bundle searchSpecBundle, int userId, IAppSearchResultCallback callback)
                 throws RemoteException {
-            ignore(callback);
+            final String key = getKey(userId, databaseName);
+            if (!mDocumentMap.containsKey(key)) {
+                final Bundle page = new Bundle();
+                page.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, 1);
+                page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, new ArrayList<>());
+                callback.onResult(AppSearchResult.newSuccessfulResult(page));
+                return;
+            }
+            final List<GenericDocument> documents = new ArrayList<>(mDocumentMap.get(key).values());
+            final Bundle page = new Bundle();
+            page.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, 0);
+            final ArrayList<Bundle> resultBundles = new ArrayList<>();
+            for (GenericDocument document : documents) {
+                final Bundle resultBundle = new Bundle();
+                resultBundle.putBundle("document", document.getBundle());
+                resultBundle.putString("packageName", packageName);
+                resultBundle.putString("databaseName", databaseName);
+                resultBundle.putParcelableArrayList("matches", new ArrayList<>());
+                resultBundles.add(resultBundle);
+            }
+            page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, resultBundles);
+            callback.onResult(AppSearchResult.newSuccessfulResult(page));
         }
 
         @Override
@@ -679,7 +751,10 @@
         @Override
         public void getNextPage(long nextPageToken, int userId, IAppSearchResultCallback callback)
                 throws RemoteException {
-            ignore(callback);
+            final Bundle page = new Bundle();
+            page.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, 1);
+            page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, new ArrayList<>());
+            callback.onResult(AppSearchResult.newSuccessfulResult(page));
         }
 
         @Override
@@ -698,14 +773,40 @@
         public void removeByUri(String packageName, String databaseName, String namespace,
                 List<String> uris, int userId, IAppSearchBatchResultCallback callback)
                 throws RemoteException {
-            ignore(callback);
+            final AppSearchBatchResult.Builder<String, Void> builder =
+                    new AppSearchBatchResult.Builder<>();
+            final String key = getKey(userId, databaseName);
+            if (!mDocumentMap.containsKey(key)) {
+                for (String uri : uris) {
+                    builder.setFailure(uri, AppSearchResult.RESULT_NOT_FOUND,
+                            "package " + key + " not found when removing " + uri);
+                }
+            } else {
+                final Map<String, GenericDocument> docs = mDocumentMap.get(key);
+                for (String uri : uris) {
+                    if (docs.containsKey(uri)) {
+                        docs.remove(uri);
+                        builder.setSuccess(uri, null);
+                    } else {
+                        builder.setFailure(uri, AppSearchResult.RESULT_NOT_FOUND,
+                                "shortcut not found when removing " + uri);
+                    }
+                }
+            }
+            callback.onResult(builder.build());
         }
 
         @Override
         public void removeByQuery(String packageName, String databaseName, String queryExpression,
                 Bundle searchSpecBundle, int userId, IAppSearchResultCallback callback)
                 throws RemoteException {
-            ignore(callback);
+            final String key = getKey(userId, databaseName);
+            if (!mDocumentMap.containsKey(key)) {
+                callback.onResult(AppSearchResult.newSuccessfulResult(null));
+                return;
+            }
+            mDocumentMap.get(key).clear();
+            callback.onResult(AppSearchResult.newSuccessfulResult(null));
         }
 
         @Override
@@ -724,12 +825,12 @@
             return null;
         }
 
-        private void ignore(IAppSearchResultCallback callback) throws RemoteException {
-            callback.onResult(AppSearchResult.newSuccessfulResult(null));
+        private void removeShortcuts() {
+            mDocumentMap.clear();
         }
 
-        private void ignore(IAppSearchBatchResultCallback callback) throws RemoteException {
-            callback.onResult(new AppSearchBatchResult.Builder().build());
+        private void ignore(IAppSearchResultCallback callback) throws RemoteException {
+            callback.onResult(AppSearchResult.newSuccessfulResult(null));
         }
     }
 
@@ -1146,6 +1247,9 @@
 
         shutdownServices();
 
+        mMockAppSearchManager.removeShortcuts();
+        mMockAppSearchManager = null;
+
         super.tearDown();
     }
 
@@ -1891,6 +1995,11 @@
         return mService.getPackageShortcutForTest(packageName, shortcutId, userId);
     }
 
+    protected void updatePackageShortcut(String packageName, String shortcutId, int userId,
+            Consumer<ShortcutInfo> cb) {
+        mService.updatePackageShortcutForTest(packageName, shortcutId, userId, cb);
+    }
+
     protected void assertShortcutExists(String packageName, String shortcutId, int userId) {
         assertTrue(getPackageShortcut(packageName, shortcutId, userId) != null);
     }
@@ -2086,6 +2195,10 @@
         return getPackageShortcut(getCallingPackage(), shortcutId, getCallingUserId());
     }
 
+    protected void updateCallerShortcut(String shortcutId, Consumer<ShortcutInfo> cb) {
+        updatePackageShortcut(getCallingPackage(), shortcutId, getCallingUserId(), cb);
+    }
+
     protected List<ShortcutInfo> getLauncherShortcuts(String launcher, int userId, int queryFlags) {
         final List<ShortcutInfo>[] ret = new List[1];
         runWithCaller(launcher, userId, () -> {
@@ -2245,6 +2358,8 @@
 
         deleteAllSavedFiles();
 
+        mMockAppSearchManager.removeShortcuts();
+
         initService();
         mService.applyRestore(payload, USER_0);
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 4d0beef..3f680e6 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -1385,6 +1385,7 @@
             mService.waitForBitmapSavesForTest();
             assertWith(getCallerShortcuts())
                     .forShortcutWithId("s1", si -> {
+                        Log.d("ShortcutManagerTest1", si.toString());
                         assertTrue(si.hasIconFile());
                     });
 
@@ -1702,8 +1703,8 @@
 
         // Because setDynamicShortcuts will update the timestamps when ranks are changing,
         // we explicitly set timestamps here.
-        getCallerShortcut("s1").setTimestamp(5000);
-        getCallerShortcut("s2").setTimestamp(1000);
+        updateCallerShortcut("s1", si -> si.setTimestamp(5000));
+        updateCallerShortcut("s2", si -> si.setTimestamp(1000));
 
         setCaller(CALLING_PACKAGE_2);
         final ShortcutInfo s2_2 = makeShortcut("s2");
@@ -1713,9 +1714,9 @@
                 makeComponent(ShortcutActivity.class));
         assertTrue(mManager.setDynamicShortcuts(list(s2_2, s2_3, s2_4)));
 
-        getCallerShortcut("s2").setTimestamp(1500);
-        getCallerShortcut("s3").setTimestamp(3000);
-        getCallerShortcut("s4").setTimestamp(500);
+        updateCallerShortcut("s2", si -> si.setTimestamp(1500));
+        updateCallerShortcut("s3", si -> si.setTimestamp(3000));
+        updateCallerShortcut("s4", si -> si.setTimestamp(500));
 
         setCaller(CALLING_PACKAGE_3);
         final ShortcutInfo s3_2 = makeShortcutWithLocusId("s3", makeLocusId("l2"));
@@ -1723,7 +1724,7 @@
 
         assertTrue(mManager.setDynamicShortcuts(list(s3_2)));
 
-        getCallerShortcut("s3").setTimestamp(START_TIME + 5000);
+        updateCallerShortcut("s3", si -> si.setTimestamp(START_TIME + 5000));
 
         setCaller(LAUNCHER_1);
 
@@ -7686,7 +7687,7 @@
                         assertEquals("http://www/", si.getIntent().getData().toString());
                         assertEquals("foo/bar", si.getIntent().getType());
                         assertEquals(
-                                new ComponentName("abc", ".xyz"), si.getIntent().getComponent());
+                                new ComponentName("abc", "abc.xyz"), si.getIntent().getComponent());
 
                         assertEquals(set("cat1", "cat2"), si.getIntent().getCategories());
                         assertEquals("value1", si.getIntent().getStringExtra("key1"));
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 395b643..fc26611 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -58,6 +58,7 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
 
 import javax.annotation.concurrent.GuardedBy;
 
@@ -158,6 +159,40 @@
         fail("Didn't find a guest: " + list);
     }
 
+    @Test
+    public void testCloneUser() throws Exception {
+        // Test that only one clone user can be created
+        final int primaryUserId = mUserManager.getPrimaryUser().id;
+        UserInfo userInfo = createProfileForUser("Clone user1",
+                UserManager.USER_TYPE_PROFILE_CLONE,
+                primaryUserId);
+        assertThat(userInfo).isNotNull();
+        UserInfo userInfo2 = createProfileForUser("Clone user2",
+                UserManager.USER_TYPE_PROFILE_CLONE,
+                primaryUserId);
+        assertThat(userInfo2).isNull();
+
+        final Context userContext = mContext.createPackageContextAsUser("system", 0,
+                UserHandle.of(userInfo.id));
+        assertThat(userContext.getSystemService(
+                UserManager.class).sharesMediaWithParent()).isTrue();
+
+        List<UserInfo> list = mUserManager.getUsers();
+        List<UserInfo> cloneUsers = list.stream().filter(
+                user -> (user.id == userInfo.id && user.name.equals("Clone user1")
+                        && user.isCloneProfile()))
+                .collect(Collectors.toList());
+        assertThat(cloneUsers.size()).isEqualTo(1);
+
+        // Verify clone user parent
+        assertThat(mUserManager.getProfileParent(primaryUserId)).isNull();
+        UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id);
+        assertThat(parentProfileInfo).isNotNull();
+        assertThat(primaryUserId).isEqualTo(parentProfileInfo.id);
+        removeUser(userInfo.id);
+        assertThat(mUserManager.getProfileParent(primaryUserId)).isNull();
+    }
+
     @MediumTest
     @Test
     public void testAdd2Users() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
index 5a100a2..a0e9d97 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
@@ -72,7 +72,6 @@
     private TestCallback mTestCallback;
     private TestLocationTimeZoneProvider mTestPrimaryLocationTimeZoneProvider;
     private TestLocationTimeZoneProvider mTestSecondaryLocationTimeZoneProvider;
-    private FakeTimeZoneIdValidator mTimeZoneAvailabilityChecker;
 
     @Before
     public void setUp() {
@@ -84,13 +83,10 @@
         };
         mTestThreadingDomain = new TestThreadingDomain();
         mTestCallback = new TestCallback(mTestThreadingDomain);
-        mTimeZoneAvailabilityChecker = new FakeTimeZoneIdValidator();
         mTestPrimaryLocationTimeZoneProvider = new TestLocationTimeZoneProvider(
-                stubbedProviderMetricsLogger, mTestThreadingDomain, "primary",
-                mTimeZoneAvailabilityChecker);
+                stubbedProviderMetricsLogger, mTestThreadingDomain, "primary");
         mTestSecondaryLocationTimeZoneProvider = new TestLocationTimeZoneProvider(
-                stubbedProviderMetricsLogger, mTestThreadingDomain, "secondary",
-                mTimeZoneAvailabilityChecker);
+                stubbedProviderMetricsLogger, mTestThreadingDomain, "secondary");
     }
 
     @Test
@@ -1185,10 +1181,9 @@
          * Creates the instance.
          */
         TestLocationTimeZoneProvider(ProviderMetricsLogger providerMetricsLogger,
-                ThreadingDomain threadingDomain, String providerName,
-                TimeZoneIdValidator timeZoneIdValidator) {
+                ThreadingDomain threadingDomain, String providerName) {
             super(providerMetricsLogger, threadingDomain, providerName,
-                    timeZoneIdValidator);
+                    new FakeTimeZoneProviderEventPreProcessor());
         }
 
         public void setFailDuringInitialization(boolean failInitialization) {
@@ -1321,14 +1316,4 @@
             mTestProviderState.commitLatest();
         }
     }
-
-    private static final class FakeTimeZoneIdValidator
-            implements LocationTimeZoneProvider.TimeZoneIdValidator {
-
-        @Override
-        public boolean isValid(@NonNull String timeZoneId) {
-            return true;
-        }
-
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java
new file mode 100644
index 0000000..e75d05c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java
@@ -0,0 +1,39 @@
+/*
+ * 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.timezonedetector.location;
+
+/**
+ * Fake implementation of {@link TimeZoneProviderEventPreProcessor} which assumes that all events
+ * are valid or always uncertain if {@link #enterUncertainMode()} was called.
+ */
+public final class FakeTimeZoneProviderEventPreProcessor
+        implements TimeZoneProviderEventPreProcessor {
+
+    private boolean mIsUncertain = false;
+
+    @Override
+    public TimeZoneProviderEvent preProcess(TimeZoneProviderEvent timeZoneProviderEvent) {
+        if (mIsUncertain) {
+            return TimeZoneProviderEvent.createUncertainEvent();
+        }
+        return timeZoneProviderEvent;
+    }
+
+    public void enterUncertainMode() {
+        mIsUncertain = true;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
index d13a04e..0edb559 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
@@ -52,10 +52,8 @@
 
 import java.time.Duration;
 import java.util.Arrays;
-import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
@@ -68,13 +66,13 @@
 
     private TestThreadingDomain mTestThreadingDomain;
     private TestProviderListener mProviderListener;
-    private FakeTimeZoneIdValidator mTimeZoneAvailabilityChecker;
+    private FakeTimeZoneProviderEventPreProcessor mTimeZoneProviderEventPreProcessor;
 
     @Before
     public void setUp() {
         mTestThreadingDomain = new TestThreadingDomain();
         mProviderListener = new TestProviderListener();
-        mTimeZoneAvailabilityChecker = new FakeTimeZoneIdValidator();
+        mTimeZoneProviderEventPreProcessor = new FakeTimeZoneProviderEventPreProcessor();
     }
 
     @Test
@@ -82,9 +80,10 @@
         String providerName = "arbitrary";
         RecordingProviderMetricsLogger providerMetricsLogger = new RecordingProviderMetricsLogger();
         TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider(
-                providerMetricsLogger, mTestThreadingDomain, providerName,
-                mTimeZoneAvailabilityChecker);
-        mTimeZoneAvailabilityChecker.validIds("Europe/London");
+                providerMetricsLogger,
+                mTestThreadingDomain,
+                providerName,
+                mTimeZoneProviderEventPreProcessor);
 
         // initialize()
         provider.initialize(mProviderListener);
@@ -174,8 +173,10 @@
         String providerName = "primary";
         StubbedProviderMetricsLogger providerMetricsLogger = new StubbedProviderMetricsLogger();
         TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider(
-                providerMetricsLogger, mTestThreadingDomain, providerName,
-                mTimeZoneAvailabilityChecker);
+                providerMetricsLogger,
+                mTestThreadingDomain,
+                providerName,
+                mTimeZoneProviderEventPreProcessor);
 
         TestCommand testCommand = TestCommand.createForTests("test", new Bundle());
         AtomicReference<Bundle> resultReference = new AtomicReference<>();
@@ -193,10 +194,11 @@
         String providerName = "primary";
         StubbedProviderMetricsLogger providerMetricsLogger = new StubbedProviderMetricsLogger();
         TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider(
-                providerMetricsLogger, mTestThreadingDomain, providerName,
-                mTimeZoneAvailabilityChecker);
+                providerMetricsLogger,
+                mTestThreadingDomain,
+                providerName,
+                mTimeZoneProviderEventPreProcessor);
         provider.setStateChangeRecordingEnabled(true);
-        mTimeZoneAvailabilityChecker.validIds("Europe/London");
 
         // initialize()
         provider.initialize(mProviderListener);
@@ -234,14 +236,17 @@
     }
 
     @Test
-    public void considerSuggestionWithInvalidTimeZoneIdsAsUncertain() {
+    public void entersUncertainState_whenEventHasUnsupportedZones() {
         String providerName = "primary";
         StubbedProviderMetricsLogger providerMetricsLogger = new StubbedProviderMetricsLogger();
         TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider(
-                providerMetricsLogger, mTestThreadingDomain, providerName,
-                mTimeZoneAvailabilityChecker);
+                providerMetricsLogger,
+                mTestThreadingDomain,
+                providerName,
+                mTimeZoneProviderEventPreProcessor);
         provider.setStateChangeRecordingEnabled(true);
         provider.initialize(mProviderListener);
+        mTimeZoneProviderEventPreProcessor.enterUncertainMode();
 
         ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
         Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
@@ -309,8 +314,9 @@
         TestLocationTimeZoneProvider(@NonNull ProviderMetricsLogger providerMetricsLogger,
                 @NonNull ThreadingDomain threadingDomain,
                 @NonNull String providerName,
-                @NonNull TimeZoneIdValidator timeZoneIdValidator) {
-            super(providerMetricsLogger, threadingDomain, providerName, timeZoneIdValidator);
+                @NonNull TimeZoneProviderEventPreProcessor timeZoneProviderEventPreProcessor) {
+            super(providerMetricsLogger,
+                    threadingDomain, providerName, timeZoneProviderEventPreProcessor);
         }
 
         @Override
@@ -367,20 +373,6 @@
         }
     }
 
-    private static final class FakeTimeZoneIdValidator
-            implements LocationTimeZoneProvider.TimeZoneIdValidator {
-        private final Set<String> mValidTimeZoneIds = new HashSet<>();
-
-        @Override
-        public boolean isValid(@NonNull String timeZoneId) {
-            return mValidTimeZoneIds.contains(timeZoneId);
-        }
-
-        public void validIds(String... timeZoneIdss) {
-            mValidTimeZoneIds.addAll(asList(timeZoneIdss));
-        }
-    }
-
     private static class StubbedProviderMetricsLogger implements
             LocationTimeZoneProvider.ProviderMetricsLogger {
 
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidatorTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidatorTest.java
deleted file mode 100644
index 5561b2c..0000000
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidatorTest.java
+++ /dev/null
@@ -1,54 +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.timezonedetector.location;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.platform.test.annotations.Presubmit;
-
-import org.junit.Test;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.TimeZone;
-
-@Presubmit
-public class ZoneInfoDbTimeZoneIdValidatorTest {
-    private final LocationTimeZoneProvider.TimeZoneIdValidator mTzChecker =
-            new ZoneInfoDbTimeZoneIdValidator();
-
-    @Test
-    public void timeZoneIdsFromZoneInfoDbAreValid() {
-        for (String timeZone : TimeZone.getAvailableIDs()) {
-            assertWithMessage("Time zone %s should be supported", timeZone)
-                    .that(mTzChecker.isValid(timeZone)).isTrue();
-        }
-    }
-
-    @Test
-    public void nonExistingZones_areNotSupported() {
-        List<String> nonExistingTimeZones = Arrays.asList(
-                "SystemV/HST10", "Atlantic/Atlantis", "EUROPE/LONDON", "Etc/GMT-5:30"
-        );
-
-        for (String timeZone : nonExistingTimeZones) {
-            assertWithMessage(timeZone + " is not a valid time zone")
-                    .that(mTzChecker.isValid(timeZone))
-                    .isFalse();
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
new file mode 100644
index 0000000..173705be
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.timezonedetector.location;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.platform.test.annotations.Presubmit;
+import android.service.timezone.TimeZoneProviderSuggestion;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.TimeZone;
+
+/** Tests for {@link ZoneInfoDbTimeZoneProviderEventPreProcessor}. */
+@Presubmit
+public class ZoneInfoDbTimeZoneProviderEventPreProcessorTest {
+
+    private static final long ARBITRARY_TIME_MILLIS = 11223344;
+
+    private final ZoneInfoDbTimeZoneProviderEventPreProcessor mPreProcessor =
+            new ZoneInfoDbTimeZoneProviderEventPreProcessor();
+
+    @Test
+    public void timeZoneIdsFromZoneInfoDbAreValid() {
+        for (String timeZone : TimeZone.getAvailableIDs()) {
+            TimeZoneProviderEvent event = timeZoneProviderEvent(timeZone);
+            assertWithMessage("Time zone %s should be supported", timeZone)
+                    .that(mPreProcessor.preProcess(event)).isEqualTo(event);
+        }
+    }
+
+    @Test
+    public void eventWithNonExistingZones_areMappedToUncertainEvent() {
+        List<String> nonExistingTimeZones = Arrays.asList(
+                "SystemV/HST10", "Atlantic/Atlantis", "EUROPE/LONDON", "Etc/GMT-5:30");
+
+        for (String timeZone : nonExistingTimeZones) {
+            TimeZoneProviderEvent event = timeZoneProviderEvent(timeZone);
+
+            assertWithMessage(timeZone + " is not a valid time zone")
+                    .that(mPreProcessor.preProcess(event))
+                    .isEqualTo(TimeZoneProviderEvent.createUncertainEvent());
+        }
+    }
+
+    private static TimeZoneProviderEvent timeZoneProviderEvent(String... timeZoneIds) {
+        return TimeZoneProviderEvent.createSuggestionEvent(
+                new TimeZoneProviderSuggestion.Builder()
+                        .setTimeZoneIds(Arrays.asList(timeZoneIds))
+                        .setElapsedRealtimeMillis(ARBITRARY_TIME_MILLIS)
+                .build());
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
index b54b696..a959a5e 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -20,6 +20,10 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.VibrationEffect;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
 
 import com.android.server.vibrator.VibratorController.OnVibrationCompleteListener;
 
@@ -37,9 +41,9 @@
 
     private static final int EFFECT_DURATION = 20;
 
-    private final Map<Long, VibrationEffect.Prebaked> mEnabledAlwaysOnEffects = new HashMap<>();
-    private final List<VibrationEffect> mEffects = new ArrayList<>();
-    private final List<Integer> mAmplitudes = new ArrayList<>();
+    private final Map<Long, PrebakedSegment> mEnabledAlwaysOnEffects = new HashMap<>();
+    private final List<VibrationEffectSegment> mEffectSegments = new ArrayList<>();
+    private final List<Float> mAmplitudes = new ArrayList<>();
     private final Handler mHandler;
     private final FakeNativeWrapper mNativeWrapper;
 
@@ -57,85 +61,96 @@
         public OnVibrationCompleteListener listener;
         public boolean isInitialized;
 
+        @Override
         public void init(int vibratorId, OnVibrationCompleteListener listener) {
             isInitialized = true;
             this.vibratorId = vibratorId;
             this.listener = listener;
         }
 
+        @Override
         public boolean isAvailable() {
             return mIsAvailable;
         }
 
+        @Override
         public void on(long milliseconds, long vibrationId) {
-            VibrationEffect effect = VibrationEffect.createOneShot(
-                    milliseconds, VibrationEffect.DEFAULT_AMPLITUDE);
-            mEffects.add(effect);
+            mEffectSegments.add(
+                    new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, (int) milliseconds));
             applyLatency();
             scheduleListener(milliseconds, vibrationId);
         }
 
+        @Override
         public void off() {
         }
 
-        public void setAmplitude(int amplitude) {
+        @Override
+        public void setAmplitude(float amplitude) {
             mAmplitudes.add(amplitude);
             applyLatency();
         }
 
+        @Override
         public int[] getSupportedEffects() {
             return mSupportedEffects;
         }
 
+        @Override
         public int[] getSupportedPrimitives() {
             return mSupportedPrimitives;
         }
 
+        @Override
         public float getResonantFrequency() {
             return mResonantFrequency;
         }
 
+        @Override
         public float getQFactor() {
             return mQFactor;
         }
 
+        @Override
         public long perform(long effect, long strength, long vibrationId) {
             if (mSupportedEffects == null
                     || Arrays.binarySearch(mSupportedEffects, (int) effect) < 0) {
                 return 0;
             }
-            mEffects.add(new VibrationEffect.Prebaked((int) effect, false, (int) strength));
+            mEffectSegments.add(new PrebakedSegment((int) effect, false, (int) strength));
             applyLatency();
             scheduleListener(EFFECT_DURATION, vibrationId);
             return EFFECT_DURATION;
         }
 
-        public long compose(VibrationEffect.Composition.PrimitiveEffect[] effect,
-                long vibrationId) {
-            VibrationEffect.Composed composed = new VibrationEffect.Composed(Arrays.asList(effect));
-            mEffects.add(composed);
-            applyLatency();
+        @Override
+        public long compose(PrimitiveSegment[] effects, long vibrationId) {
             long duration = 0;
-            for (VibrationEffect.Composition.PrimitiveEffect e : effect) {
-                duration += EFFECT_DURATION + e.delay;
+            for (PrimitiveSegment primitive : effects) {
+                duration += EFFECT_DURATION + primitive.getDelay();
+                mEffectSegments.add(primitive);
             }
+            applyLatency();
             scheduleListener(duration, vibrationId);
             return duration;
         }
 
+        @Override
         public void setExternalControl(boolean enabled) {
         }
 
+        @Override
         public long getCapabilities() {
             return mCapabilities;
         }
 
+        @Override
         public void alwaysOnEnable(long id, long effect, long strength) {
-            VibrationEffect.Prebaked prebaked = new VibrationEffect.Prebaked((int) effect, false,
-                    (int) strength);
+            PrebakedSegment prebaked = new PrebakedSegment((int) effect, false, (int) strength);
             mEnabledAlwaysOnEffects.put(id, prebaked);
         }
 
+        @Override
         public void alwaysOnDisable(long id) {
             mEnabledAlwaysOnEffects.remove(id);
         }
@@ -222,21 +237,21 @@
      * Return the amplitudes set by this controller, including zeroes for each time the vibrator was
      * turned off.
      */
-    public List<Integer> getAmplitudes() {
+    public List<Float> getAmplitudes() {
         return new ArrayList<>(mAmplitudes);
     }
 
-    /** Return list of {@link VibrationEffect} played by this controller, in order. */
-    public List<VibrationEffect> getEffects() {
-        return new ArrayList<>(mEffects);
+    /** Return list of {@link VibrationEffectSegment} played by this controller, in order. */
+    public List<VibrationEffectSegment> getEffectSegments() {
+        return new ArrayList<>(mEffectSegments);
     }
 
     /**
-     * Return the {@link VibrationEffect.Prebaked} effect enabled with given id, or {@code null} if
+     * Return the {@link PrebakedSegment} effect enabled with given id, or {@code null} if
      * missing or disabled.
      */
     @Nullable
-    public VibrationEffect.Prebaked getAlwaysOnEffect(int id) {
+    public PrebakedSegment getAlwaysOnEffect(int id) {
         return mEnabledAlwaysOnEffects.get((long) id);
     }
 }
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 b6c11fe..59c0b0e 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java
@@ -26,7 +26,6 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.ContextWrapper;
-import android.os.CombinedVibrationEffect;
 import android.os.Handler;
 import android.os.IExternalVibratorService;
 import android.os.PowerManagerInternal;
@@ -35,6 +34,10 @@
 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.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 
@@ -130,51 +133,13 @@
     }
 
     @Test
-    public void scale_withCombined_resolvesAndScalesRecursively() {
+    public void scale_withPrebakedSegment_setsEffectStrengthBasedOnSettings() {
         setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
                 Vibrator.VIBRATION_INTENSITY_HIGH);
-        VibrationEffect prebaked = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
-        VibrationEffect oneShot = VibrationEffect.createOneShot(10, 10);
+        PrebakedSegment effect = new PrebakedSegment(VibrationEffect.EFFECT_CLICK,
+                /* shouldFallback= */ false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
 
-        CombinedVibrationEffect.Mono monoScaled = mVibrationScaler.scale(
-                CombinedVibrationEffect.createSynced(prebaked),
-                VibrationAttributes.USAGE_NOTIFICATION);
-        VibrationEffect.Prebaked prebakedScaled = (VibrationEffect.Prebaked) monoScaled.getEffect();
-        assertEquals(prebakedScaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
-
-        CombinedVibrationEffect.Stereo stereoScaled = mVibrationScaler.scale(
-                CombinedVibrationEffect.startSynced()
-                        .addVibrator(1, prebaked)
-                        .addVibrator(2, oneShot)
-                        .combine(),
-                VibrationAttributes.USAGE_NOTIFICATION);
-        prebakedScaled = (VibrationEffect.Prebaked) stereoScaled.getEffects().get(1);
-        assertEquals(prebakedScaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
-        VibrationEffect.OneShot oneshotScaled =
-                (VibrationEffect.OneShot) stereoScaled.getEffects().get(2);
-        assertTrue(oneshotScaled.getAmplitude() > 0);
-
-        CombinedVibrationEffect.Sequential sequentialScaled = mVibrationScaler.scale(
-                CombinedVibrationEffect.startSequential()
-                        .addNext(CombinedVibrationEffect.createSynced(prebaked))
-                        .addNext(CombinedVibrationEffect.createSynced(oneShot))
-                        .combine(),
-                VibrationAttributes.USAGE_NOTIFICATION);
-        monoScaled = (CombinedVibrationEffect.Mono) sequentialScaled.getEffects().get(0);
-        prebakedScaled = (VibrationEffect.Prebaked) monoScaled.getEffect();
-        assertEquals(prebakedScaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
-        monoScaled = (CombinedVibrationEffect.Mono) sequentialScaled.getEffects().get(1);
-        oneshotScaled = (VibrationEffect.OneShot) monoScaled.getEffect();
-        assertTrue(oneshotScaled.getAmplitude() > 0);
-    }
-
-    @Test
-    public void scale_withPrebaked_setsEffectStrengthBasedOnSettings() {
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_HIGH);
-        VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
-
-        VibrationEffect.Prebaked scaled = mVibrationScaler.scale(
+        PrebakedSegment scaled = mVibrationScaler.scale(
                 effect, VibrationAttributes.USAGE_NOTIFICATION);
         assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
 
@@ -196,25 +161,33 @@
     }
 
     @Test
-    public void scale_withPrebakedAndFallback_resolvesAndScalesRecursively() {
+    public void scale_withPrebakedEffect_setsEffectStrengthBasedOnSettings() {
         setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
                 Vibrator.VIBRATION_INTENSITY_HIGH);
-        VibrationEffect.OneShot fallback2 = (VibrationEffect.OneShot) VibrationEffect.createOneShot(
-                10, VibrationEffect.DEFAULT_AMPLITUDE);
-        VibrationEffect.Prebaked fallback1 = new VibrationEffect.Prebaked(
-                VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_STRENGTH_MEDIUM, fallback2);
-        VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
-                VibrationEffect.EFFECT_STRENGTH_MEDIUM, fallback1);
+        VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
 
-        VibrationEffect.Prebaked scaled = mVibrationScaler.scale(
-                effect, VibrationAttributes.USAGE_NOTIFICATION);
-        VibrationEffect.Prebaked scaledFallback1 =
-                (VibrationEffect.Prebaked) scaled.getFallbackEffect();
-        VibrationEffect.OneShot scaledFallback2 =
-                (VibrationEffect.OneShot) scaledFallback1.getFallbackEffect();
+        PrebakedSegment scaled = getFirstSegment(mVibrationScaler.scale(
+                effect, VibrationAttributes.USAGE_NOTIFICATION));
         assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
-        assertEquals(scaledFallback1.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
-        assertTrue(scaledFallback2.getAmplitude() > 0);
+
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_MEDIUM);
+        scaled = getFirstSegment(mVibrationScaler.scale(
+                effect, VibrationAttributes.USAGE_NOTIFICATION));
+        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_LOW);
+        scaled = getFirstSegment(mVibrationScaler.scale(
+                effect, VibrationAttributes.USAGE_NOTIFICATION));
+        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT);
+
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_OFF);
+        scaled = getFirstSegment(mVibrationScaler.scale(
+                effect, VibrationAttributes.USAGE_NOTIFICATION));
+        // Unexpected intensity setting will be mapped to STRONG.
+        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
     }
 
     @Test
@@ -224,20 +197,20 @@
         setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
                 Vibrator.VIBRATION_INTENSITY_LOW);
 
-        VibrationEffect.OneShot oneShot = mVibrationScaler.scale(
+        StepSegment resolved = getFirstSegment(mVibrationScaler.scale(
                 VibrationEffect.createOneShot(10, VibrationEffect.DEFAULT_AMPLITUDE),
-                VibrationAttributes.USAGE_RINGTONE);
-        assertTrue(oneShot.getAmplitude() > 0);
+                VibrationAttributes.USAGE_RINGTONE));
+        assertTrue(resolved.getAmplitude() > 0);
 
-        VibrationEffect.Waveform waveform = mVibrationScaler.scale(
+        resolved = getFirstSegment(mVibrationScaler.scale(
                 VibrationEffect.createWaveform(new long[]{10},
                         new int[]{VibrationEffect.DEFAULT_AMPLITUDE}, -1),
-                VibrationAttributes.USAGE_RINGTONE);
-        assertTrue(waveform.getAmplitudes()[0] > 0);
+                VibrationAttributes.USAGE_RINGTONE));
+        assertTrue(resolved.getAmplitude() > 0);
     }
 
     @Test
-    public void scale_withOneShotWaveform_scalesAmplitude() {
+    public void scale_withOneShotAndWaveform_scalesAmplitude() {
         mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
         setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
                 Vibrator.VIBRATION_INTENSITY_HIGH);
@@ -248,21 +221,21 @@
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
                 Vibrator.VIBRATION_INTENSITY_MEDIUM);
 
-        VibrationEffect.OneShot oneShot = mVibrationScaler.scale(
-                VibrationEffect.createOneShot(100, 100), VibrationAttributes.USAGE_RINGTONE);
+        StepSegment scaled = getFirstSegment(mVibrationScaler.scale(
+                VibrationEffect.createOneShot(128, 128), VibrationAttributes.USAGE_RINGTONE));
         // Ringtone scales up.
-        assertTrue(oneShot.getAmplitude() > 100);
+        assertTrue(scaled.getAmplitude() > 0.5);
 
-        VibrationEffect.Waveform waveform = mVibrationScaler.scale(
-                VibrationEffect.createWaveform(new long[]{100}, new int[]{100}, -1),
-                VibrationAttributes.USAGE_NOTIFICATION);
+        scaled = getFirstSegment(mVibrationScaler.scale(
+                VibrationEffect.createWaveform(new long[]{128}, new int[]{128}, -1),
+                VibrationAttributes.USAGE_NOTIFICATION));
         // Notification scales down.
-        assertTrue(waveform.getAmplitudes()[0] < 100);
+        assertTrue(scaled.getAmplitude() < 0.5);
 
-        oneShot = mVibrationScaler.scale(VibrationEffect.createOneShot(100, 100),
-                VibrationAttributes.USAGE_TOUCH);
+        scaled = getFirstSegment(mVibrationScaler.scale(VibrationEffect.createOneShot(128, 128),
+                VibrationAttributes.USAGE_TOUCH));
         // Haptic feedback does not scale.
-        assertEquals(100, oneShot.getAmplitude());
+        assertEquals(128f / 255, scaled.getAmplitude(), 1e-5);
     }
 
     @Test
@@ -280,18 +253,23 @@
         VibrationEffect composed = VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f).compose();
 
-        VibrationEffect.Composed scaled = mVibrationScaler.scale(composed,
-                VibrationAttributes.USAGE_RINGTONE);
+        PrimitiveSegment scaled = getFirstSegment(mVibrationScaler.scale(composed,
+                VibrationAttributes.USAGE_RINGTONE));
         // Ringtone scales up.
-        assertTrue(scaled.getPrimitiveEffects().get(0).scale > 0.5f);
+        assertTrue(scaled.getScale() > 0.5f);
 
-        scaled = mVibrationScaler.scale(composed, VibrationAttributes.USAGE_NOTIFICATION);
+        scaled = getFirstSegment(mVibrationScaler.scale(composed,
+                VibrationAttributes.USAGE_NOTIFICATION));
         // Notification scales down.
-        assertTrue(scaled.getPrimitiveEffects().get(0).scale < 0.5f);
+        assertTrue(scaled.getScale() < 0.5f);
 
-        scaled = mVibrationScaler.scale(composed, VibrationAttributes.USAGE_TOUCH);
+        scaled = getFirstSegment(mVibrationScaler.scale(composed, VibrationAttributes.USAGE_TOUCH));
         // Haptic feedback does not scale.
-        assertEquals(0.5, scaled.getPrimitiveEffects().get(0).scale, 1e-5);
+        assertEquals(0.5, scaled.getScale(), 1e-5);
+    }
+
+    private <T extends VibrationEffectSegment> T getFirstSegment(VibrationEffect.Composed effect) {
+        return (T) effect.getSegments().get(0);
     }
 
     private void setUserSetting(String settingName, int value) {
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 7d5eec0..739a1a3 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -40,6 +40,10 @@
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.test.TestLooper;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
 import android.platform.test.annotations.LargeTest;
 import android.platform.test.annotations.Presubmit;
 import android.util.SparseArray;
@@ -61,6 +65,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 /**
  * Tests for {@link VibrationThread}.
@@ -142,8 +147,8 @@
         assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
 
         assertEquals(Arrays.asList(expectedOneShot(10)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffects());
-        assertEquals(Arrays.asList(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
+                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
+        assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
     }
 
     @Test
@@ -161,7 +166,7 @@
         assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
 
         assertEquals(Arrays.asList(expectedOneShot(10)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffects());
+                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
         assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty());
     }
 
@@ -183,8 +188,9 @@
         assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
 
         assertEquals(Arrays.asList(expectedOneShot(15)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffects());
-        assertEquals(Arrays.asList(1, 2, 3), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
+                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
+        assertEquals(expectedAmplitudes(1, 2, 3),
+                mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
     }
 
     @Test
@@ -213,12 +219,12 @@
         verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED));
         assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
 
-        List<Integer> playedAmplitudes = fakeVibrator.getAmplitudes();
-        assertFalse(fakeVibrator.getEffects().isEmpty());
+        List<Float> playedAmplitudes = fakeVibrator.getAmplitudes();
+        assertFalse(fakeVibrator.getEffectSegments().isEmpty());
         assertFalse(playedAmplitudes.isEmpty());
 
         for (int i = 0; i < playedAmplitudes.size(); i++) {
-            assertEquals(amplitudes[i % amplitudes.length], playedAmplitudes.get(i).intValue());
+            assertEquals(amplitudes[i % amplitudes.length] / 255f, playedAmplitudes.get(i), 1e-5);
         }
     }
 
@@ -292,7 +298,7 @@
         assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
 
         assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_THUD)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffects());
+                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
     }
 
     @Test
@@ -302,9 +308,10 @@
 
         long vibrationId = 1;
         VibrationEffect fallback = VibrationEffect.createOneShot(10, 100);
-        VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
-                VibrationEffect.EFFECT_STRENGTH_STRONG, fallback);
-        VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
+        Vibration vibration = createVibration(vibrationId, CombinedVibrationEffect.createSynced(
+                VibrationEffect.get(VibrationEffect.EFFECT_CLICK)));
+        vibration.addFallback(VibrationEffect.EFFECT_CLICK, fallback);
+        VibrationThread thread = startThreadAndDispatcher(vibration);
         waitForCompletion(thread);
 
         verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
@@ -314,8 +321,8 @@
         assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
 
         assertEquals(Arrays.asList(expectedOneShot(10)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffects());
-        assertEquals(Arrays.asList(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
+                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
+        assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
     }
 
     @Test
@@ -331,7 +338,7 @@
         verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId),
                 eq(Vibration.Status.IGNORED_UNSUPPORTED));
-        assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffects().isEmpty());
+        assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
     }
 
     @Test
@@ -351,7 +358,10 @@
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED));
         assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
-        assertEquals(Arrays.asList(effect), mVibratorProviders.get(VIBRATOR_ID).getEffects());
+        assertEquals(Arrays.asList(
+                expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
+                expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0)),
+                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
     }
 
     @Test
@@ -368,7 +378,7 @@
         verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId),
                 eq(Vibration.Status.IGNORED_UNSUPPORTED));
-        assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffects().isEmpty());
+        assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
     }
 
     @Test
@@ -428,7 +438,7 @@
         assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
 
         assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_TICK)),
-                mVibratorProviders.get(VIBRATOR_ID).getEffects());
+                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments());
     }
 
     @Test
@@ -454,10 +464,10 @@
         assertFalse(thread.getVibrators().get(2).isVibrating());
         assertFalse(thread.getVibrators().get(3).isVibrating());
 
-        VibrationEffect expected = expectedPrebaked(VibrationEffect.EFFECT_CLICK);
-        assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffects());
-        assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getEffects());
-        assertEquals(Arrays.asList(expected), mVibratorProviders.get(3).getEffects());
+        VibrationEffectSegment expected = expectedPrebaked(VibrationEffect.EFFECT_CLICK);
+        assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffectSegments());
+        assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getEffectSegments());
+        assertEquals(Arrays.asList(expected), mVibratorProviders.get(3).getEffectSegments());
     }
 
     @Test
@@ -495,12 +505,16 @@
         assertFalse(thread.getVibrators().get(4).isVibrating());
 
         assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
-                mVibratorProviders.get(1).getEffects());
-        assertEquals(Arrays.asList(expectedOneShot(10)), mVibratorProviders.get(2).getEffects());
-        assertEquals(Arrays.asList(100), mVibratorProviders.get(2).getAmplitudes());
-        assertEquals(Arrays.asList(expectedOneShot(20)), mVibratorProviders.get(3).getEffects());
-        assertEquals(Arrays.asList(1, 2), mVibratorProviders.get(3).getAmplitudes());
-        assertEquals(Arrays.asList(composed), mVibratorProviders.get(4).getEffects());
+                mVibratorProviders.get(1).getEffectSegments());
+        assertEquals(Arrays.asList(expectedOneShot(10)),
+                mVibratorProviders.get(2).getEffectSegments());
+        assertEquals(expectedAmplitudes(100), mVibratorProviders.get(2).getAmplitudes());
+        assertEquals(Arrays.asList(expectedOneShot(20)),
+                mVibratorProviders.get(3).getEffectSegments());
+        assertEquals(expectedAmplitudes(1, 2), mVibratorProviders.get(3).getAmplitudes());
+        assertEquals(Arrays.asList(
+                expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
+                mVibratorProviders.get(4).getEffectSegments());
     }
 
     @Test
@@ -540,11 +554,14 @@
         assertFalse(thread.getVibrators().get(2).isVibrating());
         assertFalse(thread.getVibrators().get(3).isVibrating());
 
-        assertEquals(Arrays.asList(expectedOneShot(10)), mVibratorProviders.get(1).getEffects());
-        assertEquals(Arrays.asList(100), mVibratorProviders.get(1).getAmplitudes());
-        assertEquals(Arrays.asList(composed), mVibratorProviders.get(2).getEffects());
+        assertEquals(Arrays.asList(expectedOneShot(10)),
+                mVibratorProviders.get(1).getEffectSegments());
+        assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes());
+        assertEquals(Arrays.asList(
+                expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)),
+                mVibratorProviders.get(2).getEffectSegments());
         assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
-                mVibratorProviders.get(3).getEffects());
+                mVibratorProviders.get(3).getEffectSegments());
     }
 
     @Test
@@ -563,8 +580,9 @@
         CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(composed);
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
 
-        assertTrue(waitUntil(t -> !mVibratorProviders.get(1).getEffects().isEmpty()
-                && !mVibratorProviders.get(2).getEffects().isEmpty(), thread, TEST_TIMEOUT_MILLIS));
+        assertTrue(waitUntil(t -> !mVibratorProviders.get(1).getEffectSegments().isEmpty()
+                        && !mVibratorProviders.get(2).getEffectSegments().isEmpty(), thread,
+                TEST_TIMEOUT_MILLIS));
         thread.syncedVibrationComplete();
         waitForCompletion(thread);
 
@@ -574,8 +592,10 @@
         verify(mThreadCallbacks, never()).cancelSyncedVibration();
         verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED));
 
-        assertEquals(Arrays.asList(composed), mVibratorProviders.get(1).getEffects());
-        assertEquals(Arrays.asList(composed), mVibratorProviders.get(2).getEffects());
+        VibrationEffectSegment expected = expectedPrimitive(
+                VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100);
+        assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffectSegments());
+        assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getEffectSegments());
     }
 
     @Test
@@ -634,10 +654,12 @@
         verify(mThreadCallbacks, never()).triggerSyncedVibration(eq(vibrationId));
         verify(mThreadCallbacks, never()).cancelSyncedVibration();
 
-        assertEquals(Arrays.asList(expectedOneShot(10)), mVibratorProviders.get(1).getEffects());
-        assertEquals(Arrays.asList(100), mVibratorProviders.get(1).getAmplitudes());
-        assertEquals(Arrays.asList(expectedOneShot(5)), mVibratorProviders.get(2).getEffects());
-        assertEquals(Arrays.asList(200), mVibratorProviders.get(2).getAmplitudes());
+        assertEquals(Arrays.asList(expectedOneShot(10)),
+                mVibratorProviders.get(1).getEffectSegments());
+        assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes());
+        assertEquals(Arrays.asList(expectedOneShot(5)),
+                mVibratorProviders.get(2).getEffectSegments());
+        assertEquals(expectedAmplitudes(200), mVibratorProviders.get(2).getAmplitudes());
     }
 
     @Test
@@ -704,12 +726,15 @@
         assertFalse(thread.getVibrators().get(2).isVibrating());
         assertFalse(thread.getVibrators().get(3).isVibrating());
 
-        assertEquals(Arrays.asList(expectedOneShot(25)), mVibratorProviders.get(1).getEffects());
-        assertEquals(Arrays.asList(expectedOneShot(80)), mVibratorProviders.get(2).getEffects());
-        assertEquals(Arrays.asList(expectedOneShot(60)), mVibratorProviders.get(3).getEffects());
-        assertEquals(Arrays.asList(1, 2, 3), mVibratorProviders.get(1).getAmplitudes());
-        assertEquals(Arrays.asList(4, 5), mVibratorProviders.get(2).getAmplitudes());
-        assertEquals(Arrays.asList(6), mVibratorProviders.get(3).getAmplitudes());
+        assertEquals(Arrays.asList(expectedOneShot(25)),
+                mVibratorProviders.get(1).getEffectSegments());
+        assertEquals(Arrays.asList(expectedOneShot(80)),
+                mVibratorProviders.get(2).getEffectSegments());
+        assertEquals(Arrays.asList(expectedOneShot(60)),
+                mVibratorProviders.get(3).getEffectSegments());
+        assertEquals(expectedAmplitudes(1, 2, 3), mVibratorProviders.get(1).getAmplitudes());
+        assertEquals(expectedAmplitudes(4, 5), mVibratorProviders.get(2).getAmplitudes());
+        assertEquals(expectedAmplitudes(6), mVibratorProviders.get(3).getAmplitudes());
     }
 
     @LargeTest
@@ -826,7 +851,7 @@
         verify(mVibrationToken).linkToDeath(same(thread), eq(0));
         verify(mVibrationToken).unlinkToDeath(same(thread), eq(0));
         verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED));
-        assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffects().isEmpty());
+        assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
         assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
     }
 
@@ -843,12 +868,16 @@
 
     private VibrationThread startThreadAndDispatcher(long vibrationId,
             CombinedVibrationEffect effect) {
-        VibrationThread thread = new VibrationThread(createVibration(vibrationId, effect),
-                createVibratorControllers(), mWakeLock, mIBatteryStatsMock, mThreadCallbacks);
+        return startThreadAndDispatcher(createVibration(vibrationId, effect));
+    }
+
+    private VibrationThread startThreadAndDispatcher(Vibration vib) {
+        VibrationThread thread = new VibrationThread(vib, createVibratorControllers(), mWakeLock,
+                mIBatteryStatsMock, mThreadCallbacks);
         doAnswer(answer -> {
             thread.vibratorComplete(answer.getArgument(0));
             return null;
-        }).when(mControllerCallbacks).onComplete(anyInt(), eq(vibrationId));
+        }).when(mControllerCallbacks).onComplete(anyInt(), eq(vib.id));
         mTestLooper.startAutoDispatch();
         thread.start();
         return thread;
@@ -891,12 +920,21 @@
         return array;
     }
 
-    private VibrationEffect expectedOneShot(long millis) {
-        return VibrationEffect.createOneShot(millis, VibrationEffect.DEFAULT_AMPLITUDE);
+    private VibrationEffectSegment expectedOneShot(long millis) {
+        return new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, (int) millis);
     }
 
-    private VibrationEffect expectedPrebaked(int effectId) {
-        return new VibrationEffect.Prebaked(effectId, false,
-                VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+    private VibrationEffectSegment expectedPrebaked(int effectId) {
+        return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+    }
+
+    private VibrationEffectSegment expectedPrimitive(int primitiveId, float scale, int delay) {
+        return new PrimitiveSegment(primitiveId, scale, delay);
+    }
+
+    private List<Float> expectedAmplitudes(int... amplitudes) {
+        return Arrays.stream(amplitudes)
+                .mapToObj(amplitude -> amplitude / 255f)
+                .collect(Collectors.toList());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
index bad3e4c..0ba3a21b 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
@@ -39,6 +39,8 @@
 import android.os.IVibratorStateListener;
 import android.os.VibrationEffect;
 import android.os.test.TestLooper;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
@@ -49,7 +51,6 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.Mockito;
@@ -161,9 +162,9 @@
     @Test
     public void updateAlwaysOn_withCapability_enablesAlwaysOnEffect() {
         mockVibratorCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
-        VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked)
-                VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
-        createController().updateAlwaysOn(1, effect);
+        PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK,
+                VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+        createController().updateAlwaysOn(1, prebaked);
 
         verify(mNativeWrapperMock).alwaysOnEnable(
                 eq(1L), eq((long) VibrationEffect.EFFECT_CLICK),
@@ -179,9 +180,9 @@
 
     @Test
     public void updateAlwaysOn_withoutCapability_ignoresEffect() {
-        VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked)
-                VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
-        createController().updateAlwaysOn(1, effect);
+        PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK,
+                VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+        createController().updateAlwaysOn(1, prebaked);
 
         verify(mNativeWrapperMock, never()).alwaysOnDisable(anyLong());
         verify(mNativeWrapperMock, never()).alwaysOnEnable(anyLong(), anyLong(), anyLong());
@@ -201,9 +202,9 @@
         when(mNativeWrapperMock.perform(anyLong(), anyLong(), anyLong())).thenReturn(10L);
         VibratorController controller = createController();
 
-        VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked)
-                VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
-        assertEquals(10L, controller.on(effect, 11));
+        PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK,
+                VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+        assertEquals(10L, controller.on(prebaked, 11));
 
         assertTrue(controller.isVibrating());
         verify(mNativeWrapperMock).perform(eq((long) VibrationEffect.EFFECT_CLICK),
@@ -216,24 +217,13 @@
         when(mNativeWrapperMock.compose(any(), anyLong())).thenReturn(15L);
         VibratorController controller = createController();
 
-        VibrationEffect.Composed effect = (VibrationEffect.Composed)
-                VibrationEffect.startComposition()
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
-                        .compose();
-        assertEquals(15L, controller.on(effect, 12));
-
-        ArgumentCaptor<VibrationEffect.Composition.PrimitiveEffect[]> primitivesCaptor =
-                ArgumentCaptor.forClass(VibrationEffect.Composition.PrimitiveEffect[].class);
+        PrimitiveSegment[] primitives = new PrimitiveSegment[]{
+                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
+        };
+        assertEquals(15L, controller.on(primitives, 12));
 
         assertTrue(controller.isVibrating());
-        verify(mNativeWrapperMock).compose(primitivesCaptor.capture(), eq(12L));
-
-        // Check all primitive effect fields are passed down to the HAL.
-        assertEquals(1, primitivesCaptor.getValue().length);
-        VibrationEffect.Composition.PrimitiveEffect primitive = primitivesCaptor.getValue()[0];
-        assertEquals(VibrationEffect.Composition.PRIMITIVE_CLICK, primitive.id);
-        assertEquals(0.5f, primitive.scale, /* delta= */ 1e-2);
-        assertEquals(10, primitive.delay);
+        verify(mNativeWrapperMock).compose(eq(primitives), eq(12L));
     }
 
     @Test
@@ -286,4 +276,8 @@
     private void mockVibratorCapabilities(int capabilities) {
         when(mNativeWrapperMock.getCapabilities()).thenReturn((long) capabilities);
     }
+
+    private PrebakedSegment createPrebaked(int effectId, int effectStrength) {
+        return new PrebakedSegment(effectId, /* shouldFallback= */ false, effectStrength);
+    }
 }
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 ce6639c..12ced38 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -65,6 +65,8 @@
 import android.os.Vibrator;
 import android.os.VibratorInfo;
 import android.os.test.TestLooper;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 import android.view.InputDevice;
@@ -364,13 +366,13 @@
         assertTrue(createSystemReadyService().setAlwaysOnEffect(
                 UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS));
 
-        VibrationEffect.Prebaked expectedEffect = new VibrationEffect.Prebaked(
+        PrebakedSegment expected = new PrebakedSegment(
                 VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG);
 
         // Only vibrators 1 and 3 have always-on capabilities.
-        assertEquals(mVibratorProviders.get(1).getAlwaysOnEffect(1), expectedEffect);
+        assertEquals(mVibratorProviders.get(1).getAlwaysOnEffect(1), expected);
         assertNull(mVibratorProviders.get(2).getAlwaysOnEffect(1));
-        assertEquals(mVibratorProviders.get(3).getAlwaysOnEffect(1), expectedEffect);
+        assertEquals(mVibratorProviders.get(3).getAlwaysOnEffect(1), expected);
     }
 
     @Test
@@ -388,10 +390,10 @@
         assertTrue(createSystemReadyService().setAlwaysOnEffect(
                 UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS));
 
-        VibrationEffect.Prebaked expectedClick = new VibrationEffect.Prebaked(
+        PrebakedSegment expectedClick = new PrebakedSegment(
                 VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG);
 
-        VibrationEffect.Prebaked expectedTick = new VibrationEffect.Prebaked(
+        PrebakedSegment expectedTick = new PrebakedSegment(
                 VibrationEffect.EFFECT_TICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG);
 
         // Enables click on vibrator 1 and tick on vibrator 2 only.
@@ -487,8 +489,9 @@
         vibrate(service, VibrationEffect.createOneShot(40, 100), RINGTONE_ATTRS);
         assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
 
-        assertEquals(2, mVibratorProviders.get(1).getEffects().size());
-        assertEquals(Arrays.asList(10, 100), mVibratorProviders.get(1).getAmplitudes());
+        assertEquals(2, mVibratorProviders.get(1).getEffectSegments().size());
+        assertEquals(Arrays.asList(10 / 255f, 100 / 255f),
+                mVibratorProviders.get(1).getAmplitudes());
     }
 
     @Test
@@ -500,19 +503,19 @@
         mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
         vibrate(service, VibrationEffect.createOneShot(1, 1), HAPTIC_FEEDBACK_ATTRS);
         vibrate(service, VibrationEffect.createOneShot(2, 2), RINGTONE_ATTRS);
-        assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 1,
+        assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 1,
                 service, TEST_TIMEOUT_MILLIS));
 
         mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
         vibrate(service, VibrationEffect.createOneShot(3, 3), /* attributes= */ null);
-        assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 2,
+        assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 2,
                 service, TEST_TIMEOUT_MILLIS));
 
         vibrate(service, VibrationEffect.createOneShot(4, 4), NOTIFICATION_ATTRS);
-        assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 3,
+        assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 3,
                 service, TEST_TIMEOUT_MILLIS));
 
-        assertEquals(Arrays.asList(2, 3, 4), fakeVibrator.getAmplitudes());
+        assertEquals(Arrays.asList(2 / 255f, 3 / 255f, 4 / 255f), fakeVibrator.getAmplitudes());
     }
 
     @Test
@@ -579,7 +582,7 @@
         verify(mIInputManagerMock).vibrateCombined(eq(1), eq(effect), any());
 
         // VibrationThread will start this vibration async, so wait before checking it never played.
-        assertFalse(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(),
+        assertFalse(waitUntil(s -> !mVibratorProviders.get(1).getEffectSegments().isEmpty(),
                 service, /* timeout= */ 50));
     }
 
@@ -640,8 +643,11 @@
 
         verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
         verify(mNativeWrapperMock).triggerSynced(anyLong());
-        assertEquals(Arrays.asList(composed), mVibratorProviders.get(1).getEffects());
-        assertEquals(Arrays.asList(composed), mVibratorProviders.get(2).getEffects());
+
+        PrimitiveSegment expected = new PrimitiveSegment(
+                VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100);
+        assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffectSegments());
+        assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getEffectSegments());
     }
 
     @Test
@@ -665,7 +671,7 @@
                         .compose())
                 .combine();
         vibrate(service, effect, ALARM_ATTRS);
-        assertTrue(waitUntil(s -> !fakeVibrator1.getEffects().isEmpty(), service,
+        assertTrue(waitUntil(s -> !fakeVibrator1.getEffectSegments().isEmpty(), service,
                 TEST_TIMEOUT_MILLIS));
 
         verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
@@ -689,7 +695,7 @@
                 .addVibrator(2, VibrationEffect.createOneShot(10, 100))
                 .combine();
         vibrate(service, effect, ALARM_ATTRS);
-        assertTrue(waitUntil(s -> !fakeVibrator1.getEffects().isEmpty(), service,
+        assertTrue(waitUntil(s -> !fakeVibrator1.getEffectSegments().isEmpty(), service,
                 TEST_TIMEOUT_MILLIS));
 
         verify(mNativeWrapperMock, never()).prepareSynced(any());
@@ -709,7 +715,7 @@
                 .addVibrator(2, VibrationEffect.createOneShot(10, 100))
                 .combine();
         vibrate(service, effect, ALARM_ATTRS);
-        assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(), service,
+        assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffectSegments().isEmpty(), service,
                 TEST_TIMEOUT_MILLIS));
 
         verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
@@ -730,7 +736,7 @@
                 .addVibrator(2, VibrationEffect.createOneShot(10, 100))
                 .combine();
         vibrate(service, effect, ALARM_ATTRS);
-        assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(), service,
+        assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffectSegments().isEmpty(), service,
                 TEST_TIMEOUT_MILLIS));
 
         verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
@@ -758,40 +764,38 @@
         vibrate(service, CombinedVibrationEffect.startSynced()
                 .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
                 .combine(), ALARM_ATTRS);
-        assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 1,
+        assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 1,
                 service, TEST_TIMEOUT_MILLIS));
 
         vibrate(service, CombinedVibrationEffect.startSequential()
                 .addNext(1, VibrationEffect.createOneShot(20, 100))
                 .combine(), NOTIFICATION_ATTRS);
-        assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 2,
+        assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 2,
                 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.getEffects().size() == 3,
+        assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 4,
                 service, TEST_TIMEOUT_MILLIS));
 
         vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
 
-        assertEquals(3, fakeVibrator.getEffects().size());
+        assertEquals(4, fakeVibrator.getEffectSegments().size());
         assertEquals(1, fakeVibrator.getAmplitudes().size());
 
         // Alarm vibration is always VIBRATION_INTENSITY_HIGH.
-        VibrationEffect expected = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, false,
-                VibrationEffect.EFFECT_STRENGTH_STRONG);
-        assertEquals(expected, fakeVibrator.getEffects().get(0));
+        PrebakedSegment expected = new PrebakedSegment(
+                VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG);
+        assertEquals(expected, fakeVibrator.getEffectSegments().get(0));
 
         // Notification vibrations will be scaled with SCALE_VERY_HIGH.
-        assertTrue(150 < fakeVibrator.getAmplitudes().get(0));
+        assertTrue(0.6 < fakeVibrator.getAmplitudes().get(0));
 
         // Haptic feedback vibrations will be scaled with SCALE_LOW.
-        VibrationEffect.Composed played =
-                (VibrationEffect.Composed) fakeVibrator.getEffects().get(2);
-        assertTrue(0.5 < played.getPrimitiveEffects().get(0).scale);
-        assertTrue(0.5 > played.getPrimitiveEffects().get(1).scale);
+        assertTrue(0.5 < ((PrimitiveSegment) fakeVibrator.getEffectSegments().get(2)).getScale());
+        assertTrue(0.5 > ((PrimitiveSegment) fakeVibrator.getEffectSegments().get(3)).getScale());
 
         // Ring vibrations have intensity OFF and are not played.
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 5462f47..ff88174 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -1688,8 +1688,8 @@
 
         @Override
         public boolean matches(VibrationEffect actual) {
-            if (actual instanceof VibrationEffect.Waveform &&
-                    ((VibrationEffect.Waveform) actual).getRepeatIndex() == mRepeatIndex) {
+            if (actual instanceof VibrationEffect.Composed
+                    && ((VibrationEffect.Composed) actual).getRepeatIndex() == mRepeatIndex) {
                 return true;
             }
             // All non-waveform effects are essentially one shots.
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index b9ffd65..a37d5c8 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -31,9 +31,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.media.session.MediaSession;
 import android.os.Build;
-import android.os.Process;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
@@ -73,7 +71,6 @@
     private NotificationRecord mRecordMinCallNonInterruptive;
     private NotificationRecord mRecordMinCall;
     private NotificationRecord mRecordHighCall;
-    private NotificationRecord mRecordDefaultMedia;
     private NotificationRecord mRecordEmail;
     private NotificationRecord mRecordInlineReply;
     private NotificationRecord mRecordSms;
@@ -139,15 +136,6 @@
                 new UserHandle(userId), "", 1999), getDefaultChannel());
         mRecordHighCall.setSystemImportance(NotificationManager.IMPORTANCE_HIGH);
 
-        Notification n3 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
-                .setStyle(new Notification.MediaStyle()
-                        .setMediaSession(new MediaSession.Token(Process.myUid(), null)))
-                .build();
-        mRecordDefaultMedia = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
-                pkg2, 1, "media", uid2, uid2, n3, new UserHandle(userId),
-                "", 1499), getDefaultChannel());
-        mRecordDefaultMedia.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT);
-
         Notification n4 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
                 .setStyle(new Notification.MessagingStyle("sender!")).build();
         mRecordInlineReply = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
@@ -218,7 +206,7 @@
                 .setStyle(new Notification.MediaStyle())
                 .build();
         mNoMediaSessionMedia = new NotificationRecord(mContext, new StatusBarNotification(
-                pkg2, pkg2, 1, "cheater", uid2, uid2, n12, new UserHandle(userId),
+                pkg2, pkg2, 1, "media", uid2, uid2, n12, new UserHandle(userId),
                 "", 9258), getDefaultChannel());
         mNoMediaSessionMedia.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT);
 
@@ -247,7 +235,6 @@
         final List<NotificationRecord> expected = new ArrayList<>();
         expected.add(mRecordColorizedCall);
         expected.add(mRecordColorized);
-        expected.add(mRecordDefaultMedia);
         expected.add(mRecordHighCall);
         expected.add(mRecordInlineReply);
         if (mRecordSms != null) {
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 e37b82f..aa9feeaa 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -4910,6 +4910,7 @@
         assertEquals(0, mService.mToastQueue.size());
         mService.isSystemUid = false;
         setToastRateIsWithinQuota(true);
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
         when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -4933,6 +4934,7 @@
         assertEquals(0, mService.mToastQueue.size());
         mService.isSystemUid = false;
         setToastRateIsWithinQuota(true);
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
         when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -4952,6 +4954,7 @@
         assertEquals(0, mService.mToastQueue.size());
         mService.isSystemUid = false;
         setToastRateIsWithinQuota(true);
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
         when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -4974,11 +4977,12 @@
     }
 
     @Test
-    public void testToastRateLimiterCanPreventsShowCallForCustomToast() throws Exception {
+    public void testToastRateLimiterCanPreventShowCallForCustomToast() throws Exception {
         final String testPackage = "testPackageName";
         assertEquals(0, mService.mToastQueue.size());
         mService.isSystemUid = false;
         setToastRateIsWithinQuota(false); // rate limit reached
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
         when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -4995,12 +4999,36 @@
     }
 
     @Test
+    public void testCustomToastRateLimiterAllowsLimitAvoidanceWithPermission() throws Exception {
+        final String testPackage = "testPackageName";
+        assertEquals(0, mService.mToastQueue.size());
+        mService.isSystemUid = false;
+        setToastRateIsWithinQuota(false); // rate limit reached
+        // Avoids rate limiting.
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, true);
+
+        // package is not suspended
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+                .thenReturn(false);
+
+        setAppInForegroundForToasts(mUid, true);
+
+        Binder token = new Binder();
+        ITransientNotification callback = mock(ITransientNotification.class);
+        INotificationManager nmService = (INotificationManager) mService.mService;
+
+        nmService.enqueueToast(testPackage, token, callback, 2000, 0);
+        verify(callback).show(any());
+    }
+
+    @Test
     public void testCustomToastPostedWhileInForeground_blockedIfAppGoesToBackground()
             throws Exception {
         final String testPackage = "testPackageName";
         assertEquals(0, mService.mToastQueue.size());
         mService.isSystemUid = false;
         setToastRateIsWithinQuota(true);
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
         when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5034,6 +5062,7 @@
         assertEquals(0, mService.mToastQueue.size());
         mService.isSystemUid = false;
         setToastRateIsWithinQuota(true);
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
         when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5053,6 +5082,7 @@
         assertEquals(0, mService.mToastQueue.size());
         mService.isSystemUid = false;
         setToastRateIsWithinQuota(true);
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
         when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5072,6 +5102,7 @@
         assertEquals(0, mService.mToastQueue.size());
         mService.isSystemUid = false;
         setToastRateIsWithinQuota(true);
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
         when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5095,11 +5126,12 @@
     }
 
     @Test
-    public void testToastRateLimiterCanPreventsShowCallForTextToast() throws Exception {
+    public void testToastRateLimiterCanPreventShowCallForTextToast() throws Exception {
         final String testPackage = "testPackageName";
         assertEquals(0, mService.mToastQueue.size());
         mService.isSystemUid = false;
         setToastRateIsWithinQuota(false); // rate limit reached
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
         when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5114,12 +5146,32 @@
     }
 
     @Test
+    public void testTextToastRateLimiterAllowsLimitAvoidanceWithPermission() throws Exception {
+        final String testPackage = "testPackageName";
+        assertEquals(0, mService.mToastQueue.size());
+        mService.isSystemUid = false;
+        setToastRateIsWithinQuota(false); // rate limit reached
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, true);
+
+        // package is not suspended
+        when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+                .thenReturn(false);
+
+        Binder token = new Binder();
+        INotificationManager nmService = (INotificationManager) mService.mService;
+
+        nmService.enqueueTextToast(testPackage, token, "Text", 2000, 0, null);
+        verify(mStatusBar).showToast(anyInt(), any(), any(), any(), any(), anyInt(), any());
+    }
+
+    @Test
     public void backgroundSystemCustomToast_callsSetProcessImportantAsForegroundForToast() throws
             Exception {
         final String testPackage = "testPackageName";
         assertEquals(0, mService.mToastQueue.size());
         mService.isSystemUid = true;
         setToastRateIsWithinQuota(true);
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
         when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5145,6 +5197,7 @@
         assertEquals(0, mService.mToastQueue.size());
         mService.isSystemUid = false;
         setToastRateIsWithinQuota(true);
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
         when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5166,6 +5219,7 @@
         assertEquals(0, mService.mToastQueue.size());
         mService.isSystemUid = false;
         setToastRateIsWithinQuota(true);
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
         when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5186,6 +5240,7 @@
         assertEquals(0, mService.mToastQueue.size());
         mService.isSystemUid = false;
         setToastRateIsWithinQuota(true);
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
         when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5203,6 +5258,7 @@
         assertEquals(0, mService.mToastQueue.size());
         mService.isSystemUid = false;
         setToastRateIsWithinQuota(true);
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is suspended
         when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5224,6 +5280,7 @@
         assertEquals(0, mService.mToastQueue.size());
         mService.isSystemUid = false;
         setToastRateIsWithinQuota(true);
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
         when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5247,6 +5304,7 @@
         assertEquals(0, mService.mToastQueue.size());
         mService.isSystemUid = true;
         setToastRateIsWithinQuota(true);
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is suspended
         when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5270,6 +5328,7 @@
         assertEquals(0, mService.mToastQueue.size());
         mService.isSystemUid = false;
         setToastRateIsWithinQuota(true);
+        setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
 
         // package is not suspended
         when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5305,6 +5364,13 @@
                 .thenReturn(isWithinQuota);
     }
 
+    private void setIfPackageHasPermissionToAvoidToastRateLimiting(
+            String pkg, boolean hasPermission) throws Exception {
+        when(mPackageManager.checkPermission(android.Manifest.permission.UNLIMITED_TOASTS,
+                pkg, UserHandle.getUserId(mUid)))
+                .thenReturn(hasPermission ? PERMISSION_GRANTED : PERMISSION_DENIED);
+    }
+
     @Test
     public void testOnPanelRevealedAndHidden() {
         int items = 5;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java
index 3b15376..1116204 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java
@@ -316,9 +316,10 @@
         mScheduleCalendar.setSchedule(mScheduleInfo);
         mScheduleInfo.nextAlarm = 2000;
 
+        // next alarm updated to 3000 (alarm for 2000 was changed to 3000)
         mScheduleCalendar.maybeSetNextAlarm(1000, 3000);
 
-        assertEquals(2000, mScheduleInfo.nextAlarm);
+        assertEquals(3000, mScheduleInfo.nextAlarm);
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
index 5a527a2..7446e9e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
@@ -279,17 +279,17 @@
                 conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 500);
         assertEquals(Condition.STATE_TRUE, condition.state);
 
-        // in schedule, update with later alarm time, should be in dnd
+        // in schedule, update with nextAlarm = later alarm time (1000), should be in dnd
         condition = mService.evaluateSubscriptionLocked(
                 conditionId, cal, now.getTimeInMillis() + 250, now.getTimeInMillis() + 1000);
         assertEquals(Condition.STATE_TRUE, condition.state);
 
-        // at earliest alarm fire time, should exit dnd
-        assertTrue(cal.isInSchedule(now.getTimeInMillis() + 500));
+        // at next alarm fire time (1000), should exit dnd
+        assertTrue(cal.isInSchedule(now.getTimeInMillis() + 1000));
         assertTrue("" + info.nextAlarm + " " + now.getTimeInMillis(),
-                cal.shouldExitForAlarm(now.getTimeInMillis() + 500));
+                cal.shouldExitForAlarm(now.getTimeInMillis() + 1000));
         condition = mService.evaluateSubscriptionLocked(
-                conditionId, cal, now.getTimeInMillis() + 500, 0);
+                conditionId, cal, now.getTimeInMillis() + 1000, 0);
         assertEquals(Condition.STATE_FALSE, condition.state);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 002859e..7c9ff79 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -38,6 +38,7 @@
 import android.app.ActivityOptions.SourceInfo;
 import android.app.WaitResult;
 import android.content.Intent;
+import android.os.IBinder;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.util.ArrayMap;
@@ -402,6 +403,26 @@
     }
 
     @Test
+    public void testConsecutiveLaunchNewTask() {
+        final IBinder launchCookie = mock(IBinder.class);
+        mTrampolineActivity.noDisplay = true;
+        mTrampolineActivity.mLaunchCookie = launchCookie;
+        onActivityLaunched(mTrampolineActivity);
+        final ActivityRecord activityOnNewTask = new ActivityBuilder(mAtm)
+                .setCreateTask(true)
+                .build();
+        mActivityMetricsLogger.notifyActivityLaunching(activityOnNewTask.intent,
+                mTrampolineActivity /* caller */);
+        notifyActivityLaunched(START_SUCCESS, activityOnNewTask);
+
+        transitToDrawnAndVerifyOnLaunchFinished(activityOnNewTask);
+        assertWithMessage("Trampoline's cookie must be transferred").that(
+                mTrampolineActivity.mLaunchCookie).isNull();
+        assertWithMessage("The last launch task has the transferred cookie").that(
+                activityOnNewTask.mLaunchCookie).isEqualTo(launchCookie);
+    }
+
+    @Test
     public void testConsecutiveLaunchOnDifferentDisplay() {
         onActivityLaunched(mTopActivity);
 
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 32e5200..adf8fa4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -46,6 +46,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
 import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
@@ -1267,6 +1268,42 @@
                 is(Configuration.ORIENTATION_PORTRAIT));
     }
 
+    @Test
+    public void testHybridRotationAnimation() {
+        final DisplayContent displayContent = mDefaultDisplay;
+        final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar");
+        final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
+        final WindowState app = createWindow(null, TYPE_BASE_APPLICATION, "app");
+        final WindowState[] windows = { statusBar, navBar, app };
+        makeWindowVisible(windows);
+        final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
+        displayPolicy.addWindowLw(statusBar, statusBar.mAttrs);
+        displayPolicy.addWindowLw(navBar, navBar.mAttrs);
+        final ScreenRotationAnimation rotationAnim = new ScreenRotationAnimation(displayContent,
+                displayContent.getRotation());
+        spyOn(rotationAnim);
+        // Assume that the display rotation is changed so it is frozen in preparation for animation.
+        doReturn(true).when(rotationAnim).hasScreenshot();
+        mWm.mDisplayFrozen = true;
+        displayContent.setRotationAnimation(rotationAnim);
+        // The fade rotation animation also starts to hide some non-app windows.
+        assertNotNull(displayContent.getFadeRotationAnimationController());
+        assertTrue(statusBar.isAnimating(PARENTS, ANIMATION_TYPE_FIXED_TRANSFORM));
+
+        for (WindowState w : windows) {
+            w.setOrientationChanging(true);
+        }
+        // The display only waits for the app window to unfreeze.
+        assertFalse(displayContent.waitForUnfreeze(statusBar));
+        assertFalse(displayContent.waitForUnfreeze(navBar));
+        assertTrue(displayContent.waitForUnfreeze(app));
+        // If all windows animated by fade rotation animation have done the orientation change,
+        // the animation controller should be cleared.
+        statusBar.setOrientationChanging(false);
+        navBar.setOrientationChanging(false);
+        assertNull(displayContent.getFadeRotationAnimationController());
+    }
+
     @UseTestDisplay(addWindows = { W_ACTIVITY, W_WALLPAPER, W_STATUS_BAR, W_NAVIGATION_BAR })
     @Test
     public void testApplyTopFixedRotationTransform() {
@@ -1275,6 +1312,7 @@
         doReturn(false).when(displayPolicy).navigationBarCanMove();
         displayPolicy.addWindowLw(mStatusBarWindow, mStatusBarWindow.mAttrs);
         displayPolicy.addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
+        makeWindowVisible(mStatusBarWindow, mNavBarWindow);
         final Configuration config90 = new Configuration();
         mDisplayContent.computeScreenConfiguration(config90, ROTATION_90);
 
@@ -1297,7 +1335,7 @@
                 ROTATION_0 /* oldRotation */, ROTATION_90 /* newRotation */,
                 false /* forceUpdate */));
 
-        assertNotNull(mDisplayContent.getFixedRotationAnimationController());
+        assertNotNull(mDisplayContent.getFadeRotationAnimationController());
         assertTrue(mStatusBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS,
                 ANIMATION_TYPE_FIXED_TRANSFORM));
         assertTrue(mNavBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS,
@@ -1381,7 +1419,7 @@
         assertFalse(app.hasFixedRotationTransform());
         assertFalse(app2.hasFixedRotationTransform());
         assertEquals(config90.orientation, mDisplayContent.getConfiguration().orientation);
-        assertNull(mDisplayContent.getFixedRotationAnimationController());
+        assertNull(mDisplayContent.getFadeRotationAnimationController());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 7a4ad74..f97e794 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -556,11 +556,11 @@
     }
 
     @Test
-    public void testNotAttachNavigationBar_controlledByFixedRotationAnimation() {
+    public void testNotAttachNavigationBar_controlledByFadeRotationAnimation() {
         setupForShouldAttachNavBarDuringTransition();
-        FixedRotationAnimationController mockController =
-                mock(FixedRotationAnimationController.class);
-        doReturn(mockController).when(mDefaultDisplay).getFixedRotationAnimationController();
+        FadeRotationAnimationController mockController =
+                mock(FadeRotationAnimationController.class);
+        doReturn(mockController).when(mDefaultDisplay).getFadeRotationAnimationController();
         final ActivityRecord homeActivity = createHomeActivity();
         initializeRecentsAnimationController(mController, homeActivity);
         assertFalse(mController.isNavigationBarAttachedToApp());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 956c277..37da529 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -578,10 +578,10 @@
     }
 
     @Test
-    public void testNonAppTarget_notSendNavBar_controlledByFixedRotation() throws Exception {
-        final FixedRotationAnimationController mockController =
-                mock(FixedRotationAnimationController.class);
-        doReturn(mockController).when(mDisplayContent).getFixedRotationAnimationController();
+    public void testNonAppTarget_notSendNavBar_controlledByFadeRotation() throws Exception {
+        final FadeRotationAnimationController mockController =
+                mock(FadeRotationAnimationController.class);
+        doReturn(mockController).when(mDisplayContent).getFadeRotationAnimationController();
         final int transit = TRANSIT_OLD_TASK_OPEN;
         setupForNonAppTargetNavBar(transit, true);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
index e2585e5..16e0d90 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.view.InsetsState.ITYPE_IME;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -250,4 +251,29 @@
 
         verify(selectFunc).apply(token2.windowType, options);
     }
+
+    /**
+     * Test that {@link WindowToken#setInsetsFrozen(boolean)} will set the frozen insets
+     * states for its children windows and by default it shouldn't let IME window setting
+     * the frozen insets state even the window of the window token is the IME layering target.
+     */
+    @UseTestDisplay(addWindows = W_INPUT_METHOD)
+    @Test
+    public void testSetInsetsFrozen_notAffectImeWindowState() {
+        // Pre-condition: make the IME window be controlled by IME insets provider.
+        mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindow(
+                mDisplayContent.mInputMethodWindow, null, null);
+
+        // Simulate an app window to be the IME layering target, assume the app window has no
+        // frozen insets state by default.
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        mDisplayContent.setImeLayeringTarget(app);
+        assertNull(app.getFrozenInsetsState());
+        assertTrue(app.isImeLayeringTarget());
+
+        // Verify invoking setInsetsFrozen shouldn't let IME window setting the frozen insets state.
+        app.mToken.setInsetsFrozen(true);
+        assertNotNull(app.getFrozenInsetsState());
+        assertNull(mDisplayContent.mInputMethodWindow.getFrozenInsetsState());
+    }
 }
diff --git a/services/translation/java/com/android/server/translation/RemoteTranslationService.java b/services/translation/java/com/android/server/translation/RemoteTranslationService.java
index 0c7e617..82cb728 100644
--- a/services/translation/java/com/android/server/translation/RemoteTranslationService.java
+++ b/services/translation/java/com/android/server/translation/RemoteTranslationService.java
@@ -20,9 +20,11 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.os.ResultReceiver;
 import android.service.translation.ITranslationService;
 import android.service.translation.TranslationService;
 import android.util.Slog;
+import android.view.translation.TranslationContext;
 import android.view.translation.TranslationSpec;
 
 import com.android.internal.infra.AbstractRemoteService;
@@ -80,8 +82,14 @@
         return mIdleUnbindTimeoutMs;
     }
 
-    public void onSessionCreated(@NonNull TranslationSpec sourceSpec,
-            @NonNull TranslationSpec destSpec, int sessionId, IResultReceiver resultReceiver) {
-        run((s) -> s.onCreateTranslationSession(sourceSpec, destSpec, sessionId, resultReceiver));
+    public void onSessionCreated(@NonNull TranslationContext translationContext, int sessionId,
+            IResultReceiver resultReceiver) {
+        run((s) -> s.onCreateTranslationSession(translationContext, sessionId, resultReceiver));
+    }
+
+    public void onTranslationCapabilitiesRequest(@TranslationSpec.DataFormat int sourceFormat,
+            @TranslationSpec.DataFormat int targetFormat,
+            @NonNull ResultReceiver resultReceiver) {
+        run((s) -> s.onTranslationCapabilitiesRequest(sourceFormat, targetFormat, resultReceiver));
     }
 }
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerService.java b/services/translation/java/com/android/server/translation/TranslationManagerService.java
index 72e1e33..6203ae9 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerService.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerService.java
@@ -34,6 +34,7 @@
 import android.util.Slog;
 import android.view.autofill.AutofillId;
 import android.view.translation.ITranslationManager;
+import android.view.translation.TranslationContext;
 import android.view.translation.TranslationSpec;
 import android.view.translation.UiTranslationManager.UiTranslationState;
 
@@ -142,29 +143,33 @@
     }
 
     final class TranslationManagerServiceStub extends ITranslationManager.Stub {
+
         @Override
-        public void getSupportedLocales(IResultReceiver receiver, int userId)
+        public void onTranslationCapabilitiesRequest(@TranslationSpec.DataFormat int sourceFormat,
+                @TranslationSpec.DataFormat int targetFormat,
+                ResultReceiver receiver, int userId)
                 throws RemoteException {
             synchronized (mLock) {
                 final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
                 if (service != null && (isDefaultServiceLocked(userId)
-                        || isCalledByServiceAppLocked(userId, "getSupportedLocales"))) {
-                    service.getSupportedLocalesLocked(receiver);
+                        || isCalledByServiceAppLocked(userId, "getTranslationCapabilities"))) {
+                    service.onTranslationCapabilitiesRequestLocked(sourceFormat, targetFormat,
+                            receiver);
                 } else {
-                    Slog.v(TAG, "getSupportedLocales(): no service for " + userId);
+                    Slog.v(TAG, "onGetTranslationCapabilitiesLocked(): no service for " + userId);
                     receiver.send(STATUS_SYNC_CALL_FAIL, null);
                 }
             }
         }
 
         @Override
-        public void onSessionCreated(TranslationSpec sourceSpec, TranslationSpec destSpec,
+        public void onSessionCreated(TranslationContext translationContext,
                 int sessionId, IResultReceiver receiver, int userId) throws RemoteException {
             synchronized (mLock) {
                 final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
                 if (service != null && (isDefaultServiceLocked(userId)
                         || isCalledByServiceAppLocked(userId, "onSessionCreated"))) {
-                    service.onSessionCreatedLocked(sourceSpec, destSpec, sessionId, receiver);
+                    service.onSessionCreatedLocked(translationContext, sessionId, receiver);
                 } else {
                     Slog.v(TAG, "onSessionCreated(): no service for " + userId);
                     receiver.send(STATUS_SYNC_CALL_FAIL, null);
@@ -174,7 +179,7 @@
 
         @Override
         public void updateUiTranslationStateByTaskId(@UiTranslationState int state,
-                TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds,
+                TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
                 int taskId, int userId) {
             // deprecated
             enforceCallerHasPermission(MANAGE_UI_TRANSLATION);
@@ -183,7 +188,7 @@
                 if (service != null && (isDefaultServiceLocked(userId)
                         || isCalledByServiceAppLocked(userId,
                         "updateUiTranslationStateByTaskId"))) {
-                    service.updateUiTranslationStateLocked(state, sourceSpec, destSpec, viewIds,
+                    service.updateUiTranslationStateLocked(state, sourceSpec, targetSpec, viewIds,
                             taskId);
                 }
             }
@@ -191,14 +196,14 @@
 
         @Override
         public void updateUiTranslationState(@UiTranslationState int state,
-                TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds,
+                TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
                 IBinder token, int taskId, int userId) {
             enforceCallerHasPermission(MANAGE_UI_TRANSLATION);
             synchronized (mLock) {
                 final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
                 if (service != null && (isDefaultServiceLocked(userId)
                         || isCalledByServiceAppLocked(userId, "updateUiTranslationState"))) {
-                    service.updateUiTranslationStateLocked(state, sourceSpec, destSpec, viewIds,
+                    service.updateUiTranslationStateLocked(state, sourceSpec, targetSpec, viewIds,
                             token, taskId);
                 }
             }
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
index 1ca07cb..ee5ec47 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
@@ -16,7 +16,6 @@
 
 package com.android.server.translation;
 
-import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_SUCCESS;
 import static android.view.translation.UiTranslationManager.EXTRA_SOURCE_LOCALE;
 import static android.view.translation.UiTranslationManager.EXTRA_STATE;
 import static android.view.translation.UiTranslationManager.EXTRA_TARGET_LOCALE;
@@ -31,23 +30,23 @@
 import android.os.IRemoteCallback;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.service.translation.TranslationServiceInfo;
 import android.util.Slog;
 import android.view.autofill.AutofillId;
 import android.view.inputmethod.InputMethodInfo;
+import android.view.translation.TranslationContext;
 import android.view.translation.TranslationSpec;
 import android.view.translation.UiTranslationManager.UiTranslationState;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.IResultReceiver;
-import com.android.internal.util.SyncResultReceiver;
 import com.android.server.LocalServices;
 import com.android.server.infra.AbstractPerUserSystemService;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal.ActivityTokens;
 
-import java.util.ArrayList;
 import java.util.List;
 
 final class TranslationManagerServiceImpl extends
@@ -122,28 +121,28 @@
     }
 
     @GuardedBy("mLock")
-    void getSupportedLocalesLocked(@NonNull IResultReceiver resultReceiver) {
-        // TODO: implement this
-        try {
-            resultReceiver.send(STATUS_SYNC_CALL_SUCCESS,
-                    SyncResultReceiver.bundleFor(new ArrayList<>()));
-        } catch (RemoteException e) {
-            Slog.w(TAG, "RemoteException returning supported locales: " + e);
+    void onTranslationCapabilitiesRequestLocked(@TranslationSpec.DataFormat int sourceFormat,
+            @TranslationSpec.DataFormat int destFormat,
+            @NonNull ResultReceiver resultReceiver) {
+        final RemoteTranslationService remoteService = ensureRemoteServiceLocked();
+        if (remoteService != null) {
+            remoteService.onTranslationCapabilitiesRequest(sourceFormat, destFormat,
+                    resultReceiver);
         }
     }
 
     @GuardedBy("mLock")
-    void onSessionCreatedLocked(@NonNull TranslationSpec sourceSpec,
-            @NonNull TranslationSpec destSpec, int sessionId, IResultReceiver resultReceiver) {
+    void onSessionCreatedLocked(@NonNull TranslationContext translationContext, int sessionId,
+            IResultReceiver resultReceiver) {
         final RemoteTranslationService remoteService = ensureRemoteServiceLocked();
         if (remoteService != null) {
-            remoteService.onSessionCreated(sourceSpec, destSpec, sessionId, resultReceiver);
+            remoteService.onSessionCreated(translationContext, sessionId, resultReceiver);
         }
     }
 
     @GuardedBy("mLock")
     public void updateUiTranslationStateLocked(@UiTranslationState int state,
-            TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds,
+            TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
             int taskId) {
         // deprecated
         final ActivityTokens taskTopActivityTokens =
@@ -152,13 +151,13 @@
             Slog.w(TAG, "Unknown activity to query for update translation state.");
             return;
         }
-        updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec, destSpec,
-                viewIds);
+        updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec,
+                targetSpec, viewIds);
     }
 
     @GuardedBy("mLock")
     public void updateUiTranslationStateLocked(@UiTranslationState int state,
-            TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds,
+            TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
             IBinder token, int taskId) {
         // Get top activity for a given task id
         final ActivityTokens taskTopActivityTokens =
@@ -169,20 +168,20 @@
                     + "translation state for token=" + token + " taskId=" + taskId);
             return;
         }
-        updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec, destSpec,
-                viewIds);
+        updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec,
+                targetSpec, viewIds);
     }
 
     private void updateUiTranslationStateByActivityTokens(ActivityTokens tokens,
-            @UiTranslationState int state, TranslationSpec sourceSpec, TranslationSpec destSpec,
+            @UiTranslationState int state, TranslationSpec sourceSpec, TranslationSpec targetSpec,
             List<AutofillId> viewIds) {
         try {
             tokens.getApplicationThread().updateUiTranslationState(tokens.getActivityToken(), state,
-                    sourceSpec, destSpec, viewIds);
+                    sourceSpec, targetSpec, viewIds);
         } catch (RemoteException e) {
             Slog.w(TAG, "Update UiTranslationState fail: " + e);
         }
-        invokeCallbacks(state, sourceSpec, destSpec);
+        invokeCallbacks(state, sourceSpec, targetSpec);
     }
 
     private void invokeCallbacks(
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 05573f1..df3ca99 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -27,6 +27,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
+import android.app.AppGlobals;
 import android.app.IActivityManager;
 import android.app.IActivityTaskManager;
 import android.content.BroadcastReceiver;
@@ -35,7 +36,9 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
 import android.os.Bundle;
 import android.os.Handler;
@@ -167,12 +170,8 @@
         mSessionComponentName = new ComponentName(service.getPackageName(),
                 mInfo.getSessionService());
         final String hotwordDetectionServiceName = mInfo.getHotwordDetectionService();
-        if (hotwordDetectionServiceName != null) {
-            mHotwordDetectionComponentName = new ComponentName(service.getPackageName(),
-                    hotwordDetectionServiceName);
-        } else {
-            mHotwordDetectionComponentName = null;
-        }
+        mHotwordDetectionComponentName = hotwordDetectionServiceName != null
+                ? new ComponentName(service.getPackageName(), hotwordDetectionServiceName) : null;
         mIWindowManager = IWindowManager.Stub.asInterface(
                 ServiceManager.getService(Context.WINDOW_SERVICE));
         IntentFilter filter = new IntentFilter();
@@ -456,8 +455,22 @@
     }
 
     boolean isIsolatedProcessLocked(ComponentName componentName) {
-        // TODO : Need to make sure this component exists and is :isolated.
-        return true;
+        IPackageManager pm = AppGlobals.getPackageManager();
+        try {
+            ServiceInfo serviceInfo = pm.getServiceInfo(componentName,
+                    PackageManager.GET_META_DATA
+                            | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, mUser);
+            if (serviceInfo != null) {
+                return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
+                        && (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
+            }
+        } catch (RemoteException e) {
+            if (DEBUG) {
+                Slog.w(TAG, "isIsolatedProcess RemoteException : " + e);
+            }
+        }
+        return false;
     }
 
     public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -476,6 +489,7 @@
         pw.println("  Service info:");
         mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), "    ");
         pw.print("  Recognition service="); pw.println(mInfo.getRecognitionService());
+        pw.print("  Hotword detection service="); pw.println(mInfo.getHotwordDetectionService());
         pw.print("  Settings activity="); pw.println(mInfo.getSettingsActivity());
         pw.print("  Supports assist="); pw.println(mInfo.getSupportsAssist());
         pw.print("  Supports launch from keyguard=");
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 2a5ddfd..4e64838 100755
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -138,6 +138,27 @@
     public static final int STATE_SIMULATED_RINGING = 13;
 
     /**
+     * @hide
+     */
+    @IntDef(prefix = { "STATE_" },
+            value = {
+                    STATE_NEW,
+                    STATE_DIALING,
+                    STATE_RINGING,
+                    STATE_HOLDING,
+                    STATE_ACTIVE,
+                    STATE_DISCONNECTED,
+                    STATE_SELECT_PHONE_ACCOUNT,
+                    STATE_CONNECTING,
+                    STATE_DISCONNECTING,
+                    STATE_PULLING_CALL,
+                    STATE_AUDIO_PROCESSING,
+                    STATE_SIMULATED_RINGING
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CallState {};
+
+    /**
      * The key to retrieve the optional {@code PhoneAccount}s Telecom can bundle with its Call
      * extras. Used to pass the phone accounts to display on the front end to the user in order to
      * select phone accounts to (for example) place a call.
@@ -674,6 +695,7 @@
         // Next PROPERTY value: 0x00004000
         //******************************************************************************************
 
+        private final @CallState int mState;
         private final String mTelecomCallId;
         private final Uri mHandle;
         private final int mHandlePresentation;
@@ -868,6 +890,13 @@
             return builder.toString();
         }
 
+        /**
+         * @return the state of the {@link Call} represented by this {@link Call.Details}.
+         */
+        public final @CallState int getState() {
+            return mState;
+        }
+
         /** {@hide} */
         @TestApi
         public String getTelecomCallId() {
@@ -1069,6 +1098,7 @@
             if (o instanceof Details) {
                 Details d = (Details) o;
                 return
+                        Objects.equals(mState, d.mState) &&
                         Objects.equals(mHandle, d.mHandle) &&
                         Objects.equals(mHandlePresentation, d.mHandlePresentation) &&
                         Objects.equals(mCallerDisplayName, d.mCallerDisplayName) &&
@@ -1095,7 +1125,8 @@
 
         @Override
         public int hashCode() {
-            return Objects.hash(mHandle,
+            return Objects.hash(mState,
+                            mHandle,
                             mHandlePresentation,
                             mCallerDisplayName,
                             mCallerDisplayNamePresentation,
@@ -1117,6 +1148,7 @@
 
         /** {@hide} */
         public Details(
+                @CallState int state,
                 String telecomCallId,
                 Uri handle,
                 int handlePresentation,
@@ -1136,6 +1168,7 @@
                 String contactDisplayName,
                 int callDirection,
                 int callerNumberVerificationStatus) {
+            mState = state;
             mTelecomCallId = telecomCallId;
             mHandle = handle;
             mHandlePresentation = handlePresentation;
@@ -1160,6 +1193,7 @@
         /** {@hide} */
         public static Details createFromParcelableCall(ParcelableCall parcelableCall) {
             return new Details(
+                    parcelableCall.getState(),
                     parcelableCall.getId(),
                     parcelableCall.getHandle(),
                     parcelableCall.getHandlePresentation(),
@@ -1186,6 +1220,8 @@
             StringBuilder sb = new StringBuilder();
             sb.append("[id: ");
             sb.append(mTelecomCallId);
+            sb.append(", state: ");
+            sb.append(Call.stateToString(mState));
             sb.append(", pa: ");
             sb.append(mAccountHandle);
             sb.append(", hdl: ");
@@ -1302,7 +1338,7 @@
          * @param call The {@code Call} invoking this method.
          * @param state The new state of the {@code Call}.
          */
-        public void onStateChanged(Call call, int state) {}
+        public void onStateChanged(Call call, @CallState int state) {}
 
         /**
          * Invoked when the parent of this {@code Call} has changed. See {@link #getParent()}.
@@ -2171,9 +2207,11 @@
     /**
      * Obtains the state of this {@code Call}.
      *
-     * @return A state value, chosen from the {@code STATE_*} constants.
+     * @return The call state.
+     * @deprecated The call state is available via {@link Call.Details#getState()}.
      */
-    public int getState() {
+    @Deprecated
+    public @CallState int getState() {
         return mState;
     }
 
@@ -2551,6 +2589,30 @@
     final void internalSetDisconnected() {
         if (mState != Call.STATE_DISCONNECTED) {
             mState = Call.STATE_DISCONNECTED;
+            if (mDetails != null) {
+                mDetails = new Details(mState,
+                        mDetails.getTelecomCallId(),
+                        mDetails.getHandle(),
+                        mDetails.getHandlePresentation(),
+                        mDetails.getCallerDisplayName(),
+                        mDetails.getCallerDisplayNamePresentation(),
+                        mDetails.getAccountHandle(),
+                        mDetails.getCallCapabilities(),
+                        mDetails.getCallProperties(),
+                        mDetails.getDisconnectCause(),
+                        mDetails.getConnectTimeMillis(),
+                        mDetails.getGatewayInfo(),
+                        mDetails.getVideoState(),
+                        mDetails.getStatusHints(),
+                        mDetails.getExtras(),
+                        mDetails.getIntentExtras(),
+                        mDetails.getCreationTimeMillis(),
+                        mDetails.getContactDisplayName(),
+                        mDetails.getCallDirection(),
+                        mDetails.getCallerNumberVerificationStatus()
+                        );
+                fireDetailsChanged(mDetails);
+            }
             fireStateChanged(mState);
             fireCallDestroyed();
         }
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index 182dc8b..320308c 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -399,7 +399,7 @@
     }
 
     /** The current state of the call. */
-    public int getState() {
+    public @Call.CallState int getState() {
         return mState;
     }
 
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index 4ae11b8..23cf511 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -447,6 +447,9 @@
             DataFailCause.VSNCP_RECONNECT_NOT_ALLOWED,
             DataFailCause.IPV6_PREFIX_UNAVAILABLE,
             DataFailCause.HANDOFF_PREFERENCE_CHANGED,
+            DataFailCause.SLICE_REJECTED,
+            DataFailCause.MATCH_ALL_RULE_NOT_ALLOWED,
+            DataFailCause.ALL_MATCHING_RULES_FAILED,
             DataFailCause.OEM_DCFAILCAUSE_1,
             DataFailCause.OEM_DCFAILCAUSE_2,
             DataFailCause.OEM_DCFAILCAUSE_3,
diff --git a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
index 597fe8f..957f683 100644
--- a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
+++ b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
@@ -68,22 +68,22 @@
     public final boolean isEnDcAvailable;
 
     /**
-     * Provides network support info for LTE VoPS and LTE Emergency bearer support
+     * Provides network support info for VoPS and Emergency bearer support
      */
     @Nullable
-    private final LteVopsSupportInfo mLteVopsSupportInfo;
+    private final VopsSupportInfo mVopsSupportInfo;
 
     /**
      * @hide
      */
     DataSpecificRegistrationInfo(
             int maxDataCalls, boolean isDcNrRestricted, boolean isNrAvailable,
-            boolean isEnDcAvailable, @Nullable LteVopsSupportInfo lteVops) {
+            boolean isEnDcAvailable, @Nullable VopsSupportInfo vops) {
         this.maxDataCalls = maxDataCalls;
         this.isDcNrRestricted = isDcNrRestricted;
         this.isNrAvailable = isNrAvailable;
         this.isEnDcAvailable = isEnDcAvailable;
-        this.mLteVopsSupportInfo = lteVops;
+        this.mVopsSupportInfo = vops;
     }
 
     /**
@@ -97,7 +97,7 @@
         isDcNrRestricted = dsri.isDcNrRestricted;
         isNrAvailable = dsri.isNrAvailable;
         isEnDcAvailable = dsri.isEnDcAvailable;
-        mLteVopsSupportInfo = dsri.mLteVopsSupportInfo;
+        mVopsSupportInfo = dsri.mVopsSupportInfo;
     }
 
     private DataSpecificRegistrationInfo(/* @NonNull */ Parcel source) {
@@ -105,7 +105,7 @@
         isDcNrRestricted = source.readBoolean();
         isNrAvailable = source.readBoolean();
         isEnDcAvailable = source.readBoolean();
-        mLteVopsSupportInfo = LteVopsSupportInfo.CREATOR.createFromParcel(source);
+        mVopsSupportInfo = source.readParcelable(VopsSupportInfo.class.getClassLoader());
     }
 
     @Override
@@ -114,7 +114,7 @@
         dest.writeBoolean(isDcNrRestricted);
         dest.writeBoolean(isNrAvailable);
         dest.writeBoolean(isEnDcAvailable);
-        mLteVopsSupportInfo.writeToParcel(dest, flags);
+        dest.writeParcelable(mVopsSupportInfo, flags);
     }
 
     @Override
@@ -131,15 +131,15 @@
                 .append(" isDcNrRestricted = " + isDcNrRestricted)
                 .append(" isNrAvailable = " + isNrAvailable)
                 .append(" isEnDcAvailable = " + isEnDcAvailable)
-                .append(" " + mLteVopsSupportInfo)
+                .append(" " + mVopsSupportInfo)
                 .append(" }")
                 .toString();
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(maxDataCalls, isDcNrRestricted, isNrAvailable, isEnDcAvailable,
-                mLteVopsSupportInfo);
+        return Objects.hash(maxDataCalls, isDcNrRestricted, isNrAvailable,
+                isEnDcAvailable, mVopsSupportInfo);
     }
 
     @Override
@@ -153,7 +153,7 @@
                 && this.isDcNrRestricted == other.isDcNrRestricted
                 && this.isNrAvailable == other.isNrAvailable
                 && this.isEnDcAvailable == other.isEnDcAvailable
-                && Objects.equals(mLteVopsSupportInfo, other.mLteVopsSupportInfo);
+                && Objects.equals(mVopsSupportInfo, other.mVopsSupportInfo);
     }
 
     public static final @NonNull Parcelable.Creator<DataSpecificRegistrationInfo> CREATOR =
@@ -171,10 +171,26 @@
 
     /**
      * @return The LTE VOPS (Voice over Packet Switched) support information
+     *
+     * @deprecated use {@link #getVopsSupportInfo()}
      */
+    @Deprecated
     @NonNull
     public LteVopsSupportInfo getLteVopsSupportInfo() {
-        return mLteVopsSupportInfo;
+        return mVopsSupportInfo instanceof LteVopsSupportInfo
+                ? (LteVopsSupportInfo) mVopsSupportInfo
+                : new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE,
+                LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE);
     }
 
+    /**
+     * @return The VOPS (Voice over Packet Switched) support information.
+     *
+     * The instance of {@link LTEVopsSupportInfo}, or {@link NrVopsSupportInfo},
+     * null if there is there is no VOPS support information available.
+     */
+    @Nullable
+    public VopsSupportInfo getVopsSupportInfo() {
+        return mVopsSupportInfo;
+    }
 }
diff --git a/telephony/java/android/telephony/DataThrottlingRequest.java b/telephony/java/android/telephony/DataThrottlingRequest.java
index f50bb58..2827e8d 100644
--- a/telephony/java/android/telephony/DataThrottlingRequest.java
+++ b/telephony/java/android/telephony/DataThrottlingRequest.java
@@ -17,6 +17,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.RequiresFeature;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -52,6 +53,9 @@
      * @hide
      */
     @SystemApi
+    @RequiresFeature(
+            enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+            value = TelephonyManager.CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING)
     public static final int DATA_THROTTLING_ACTION_THROTTLE_SECONDARY_CARRIER = 1;
 
     /**
@@ -63,6 +67,9 @@
      * @hide
      */
     @SystemApi
+    @RequiresFeature(
+            enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+            value = TelephonyManager.CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING)
     public static final int DATA_THROTTLING_ACTION_THROTTLE_PRIMARY_CARRIER = 2;
 
     /**
@@ -76,6 +83,9 @@
      * @hide
      */
     @SystemApi
+    @RequiresFeature(
+            enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+            value = TelephonyManager.CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING)
     public static final int DATA_THROTTLING_ACTION_HOLD = 3;
 
     /**
diff --git a/telephony/java/android/telephony/LteVopsSupportInfo.java b/telephony/java/android/telephony/LteVopsSupportInfo.java
index 83e41bf..87761e2 100644
--- a/telephony/java/android/telephony/LteVopsSupportInfo.java
+++ b/telephony/java/android/telephony/LteVopsSupportInfo.java
@@ -21,7 +21,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
-import android.os.Parcelable;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -32,7 +32,7 @@
  * @hide
  */
 @SystemApi
-public final class LteVopsSupportInfo implements Parcelable {
+public final class LteVopsSupportInfo extends VopsSupportInfo {
 
     /**@hide*/
     @Retention(RetentionPolicy.SOURCE)
@@ -42,7 +42,10 @@
     public @interface LteVopsStatus {}
     /**
      * Indicates information not available from modem.
+     *
+     * @deprecated as no instance will be created in this case
      */
+    @Deprecated
     public static final int LTE_STATUS_NOT_AVAILABLE = 1;
 
     /**
@@ -82,13 +85,38 @@
         return mEmcBearerSupport;
     }
 
+    /**
+     * Returns whether VoPS is supported by the network
+     */
+    @Override
+    public boolean isVopsSupported() {
+        return mVopsSupport == LTE_STATUS_SUPPORTED;
+    }
+
+    /**
+     * Returns whether emergency service is supported by the network
+     */
+    @Override
+    public boolean isEmergencyServiceSupported() {
+        return mEmcBearerSupport == LTE_STATUS_SUPPORTED;
+    }
+
+    /**
+     * Returns whether emergency service fallback is supported by the network
+     */
+    @Override
+    public boolean isEmergencyServiceFallbackSupported() {
+        return false;
+    }
+
     @Override
     public int describeContents() {
         return 0;
     }
 
     @Override
-    public void writeToParcel(Parcel out, int flags) {
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        super.writeToParcel(out, flags, AccessNetworkType.EUTRAN);
         out.writeInt(mVopsSupport);
         out.writeInt(mEmcBearerSupport);
     }
@@ -124,6 +152,8 @@
             new Creator<LteVopsSupportInfo>() {
         @Override
         public LteVopsSupportInfo createFromParcel(Parcel in) {
+            // Skip the type info.
+            in.readInt();
             return new LteVopsSupportInfo(in);
         }
 
@@ -133,6 +163,11 @@
         }
     };
 
+    /** @hide */
+    protected static LteVopsSupportInfo createFromParcelBody(Parcel in) {
+        return new LteVopsSupportInfo(in);
+    }
+
     private LteVopsSupportInfo(Parcel in) {
         mVopsSupport = in.readInt();
         mEmcBearerSupport = in.readInt();
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index a78f813..5fb60d7 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -293,11 +293,12 @@
                                    @Nullable CellIdentity cellIdentity, @Nullable String rplmn,
                                    int maxDataCalls, boolean isDcNrRestricted,
                                    boolean isNrAvailable, boolean isEndcAvailable,
-                                   LteVopsSupportInfo lteVopsSupportInfo) {
+                                   @Nullable VopsSupportInfo vopsSupportInfo) {
         this(domain, transportType, registrationState, accessNetworkTechnology, rejectCause,
                 emergencyOnly, availableServices, cellIdentity, rplmn);
         mDataSpecificInfo = new DataSpecificRegistrationInfo(
-                maxDataCalls, isDcNrRestricted, isNrAvailable, isEndcAvailable, lteVopsSupportInfo);
+                maxDataCalls, isDcNrRestricted, isNrAvailable,
+                isEndcAvailable, vopsSupportInfo);
         updateNrState();
     }
 
diff --git a/telephony/java/android/telephony/NrVopsSupportInfo.aidl b/telephony/java/android/telephony/NrVopsSupportInfo.aidl
new file mode 100644
index 0000000..460a589
--- /dev/null
+++ b/telephony/java/android/telephony/NrVopsSupportInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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.telephony;
+
+parcelable NrVopsSupportInfo;
diff --git a/telephony/java/android/telephony/NrVopsSupportInfo.java b/telephony/java/android/telephony/NrVopsSupportInfo.java
new file mode 100644
index 0000000..155ee38
--- /dev/null
+++ b/telephony/java/android/telephony/NrVopsSupportInfo.java
@@ -0,0 +1,260 @@
+/*
+ * 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.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Class stores information related to NR network VoPS support
+ * @hide
+ */
+@SystemApi
+public final class NrVopsSupportInfo extends VopsSupportInfo {
+
+    /**
+     * Indicates network does not support vops
+     */
+    public static final int NR_STATUS_VOPS_NOT_SUPPORTED = 0;
+
+    /**
+     * Indicates network supports vops over 3gpp access.
+     */
+    public static final int NR_STATUS_VOPS_3GPP_SUPPORTED = 1;
+
+    /**
+     * Indicates network supports vops over non 3gpp access
+     */
+    public static final int NR_STATUS_VOPS_NON_3GPP_SUPPORTED = 2;
+
+    /**@hide*/
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+        prefix = {"NR_STATUS_VOPS_"},
+        value = {
+            NR_STATUS_VOPS_NOT_SUPPORTED,
+            NR_STATUS_VOPS_3GPP_SUPPORTED,
+            NR_STATUS_VOPS_NON_3GPP_SUPPORTED
+        })
+    public @interface NrVopsStatus {}
+
+    /**
+     * Indicates network does not support emergency service
+     */
+    public static final int NR_STATUS_EMC_NOT_SUPPORTED = 0;
+
+    /**
+     * Indicates network supports emergency service in NR connected to 5GCN only
+     */
+    public static final int NR_STATUS_EMC_5GCN_ONLY = 1;
+
+    /**
+     * Indicates network supports emergency service in E-UTRA connected to 5GCN only
+     */
+    public static final int NR_STATUS_EMC_EUTRA_5GCN_ONLY = 2;
+
+    /**
+     * Indicates network supports emergency service in NR connected to 5GCN and
+     * E-UTRA connected to 5GCN
+     */
+    public static final int NR_STATUS_EMC_NR_EUTRA_5GCN = 3;
+
+    /**@hide*/
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+        prefix = {"NR_STATUS_EMC_"},
+        value = {
+            NR_STATUS_EMC_NOT_SUPPORTED,
+            NR_STATUS_EMC_5GCN_ONLY,
+            NR_STATUS_EMC_EUTRA_5GCN_ONLY,
+            NR_STATUS_EMC_NR_EUTRA_5GCN
+        })
+    public @interface NrEmcStatus {}
+
+    /**
+     * Indicates network does not support emergency service
+     */
+    public static final int NR_STATUS_EMF_NOT_SUPPORTED = 0;
+
+    /**
+     * Indicates network supports emergency service fallback in NR connected to 5GCN only
+     */
+    public static final int NR_STATUS_EMF_5GCN_ONLY = 1;
+
+    /**
+     * Indicates network supports emergency service fallback in E-UTRA connected to 5GCN only
+     */
+    public static final int NR_STATUS_EMF_EUTRA_5GCN_ONLY = 2;
+
+    /**
+     * Indicates network supports emergency service fallback in NR connected to 5GCN
+     * and E-UTRA connected to 5GCN
+     */
+    public static final int NR_STATUS_EMF_NR_EUTRA_5GCN = 3;
+
+    /**@hide*/
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+        prefix = {"NR_STATUS_EMF_"},
+        value = {
+            NR_STATUS_EMF_NOT_SUPPORTED,
+            NR_STATUS_EMF_5GCN_ONLY,
+            NR_STATUS_EMF_EUTRA_5GCN_ONLY,
+            NR_STATUS_EMF_NR_EUTRA_5GCN
+        })
+    public @interface NrEmfStatus {}
+
+    @NrVopsStatus
+    private final int mVopsSupport;
+    @NrEmcStatus
+    private final int mEmcSupport;
+    @NrEmfStatus
+    private final int mEmfSupport;
+
+    public NrVopsSupportInfo(@NrVopsStatus int vops, @NrEmcStatus int emc, @NrEmcStatus int emf) {
+        mVopsSupport = vops;
+        mEmcSupport = emc;
+        mEmfSupport = emf;
+    }
+
+    /**
+     * Provides the NR VoPS support capability as described in:
+     * 3GPP 24.501 EPS network feature support -> IMS VoPS
+     */
+    public @NrVopsStatus int getVopsSupport() {
+        return mVopsSupport;
+    }
+
+    /**
+     * Provides the NR Emergency bearer support capability as described in:
+     * 3GPP 24.501 EPS network feature support -> EMC, and
+     * 38.331 SIB1 : ims-EmergencySupport
+     */
+    public @NrEmcStatus int getEmcSupport() {
+        return mEmcSupport;
+    }
+
+    /**
+     * Provides the NR emergency service fallback support capability as
+     * described in 3GPP 24.501 EPS network feature support -> EMF
+     */
+    public @NrEmfStatus int getEmfSupport() {
+        return mEmfSupport;
+    }
+
+    /**
+     * Returns whether VoPS is supported by the network
+     */
+    @Override
+    public boolean isVopsSupported() {
+        return mVopsSupport != NR_STATUS_VOPS_NOT_SUPPORTED;
+    }
+
+    /**
+     * Returns whether emergency service is supported by the network
+     */
+    @Override
+    public boolean isEmergencyServiceSupported() {
+        return mEmcSupport != NR_STATUS_EMC_NOT_SUPPORTED;
+    }
+
+    /**
+     * Returns whether emergency service fallback is supported by the network
+     */
+    public boolean isEmergencyServiceFallbackSupported() {
+        return mEmfSupport != NR_STATUS_EMF_NOT_SUPPORTED;
+    }
+
+    /**
+     * Implement the Parcelable interface
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        super.writeToParcel(out, flags, AccessNetworkType.NGRAN);
+        out.writeInt(mVopsSupport);
+        out.writeInt(mEmcSupport);
+        out.writeInt(mEmfSupport);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (o == null || !(o instanceof NrVopsSupportInfo)) {
+            return false;
+        }
+        if (this == o) return true;
+        NrVopsSupportInfo other = (NrVopsSupportInfo) o;
+        return mVopsSupport == other.mVopsSupport
+            && mEmcSupport == other.mEmcSupport
+            && mEmfSupport == other.mEmfSupport;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mVopsSupport, mEmcSupport, mEmfSupport);
+    }
+
+    /**
+     * @return string representation.
+     */
+    @NonNull
+    @Override
+    public String toString() {
+        return ("NrVopsSupportInfo : "
+                + " mVopsSupport = " + mVopsSupport
+                + " mEmcSupport = " + mEmcSupport
+                + " mEmfSupport = " + mEmfSupport);
+    }
+
+    public static final @android.annotation.NonNull Creator<NrVopsSupportInfo> CREATOR =
+            new Creator<NrVopsSupportInfo>() {
+        @Override
+        public NrVopsSupportInfo createFromParcel(Parcel in) {
+            // Skip the type info.
+            in.readInt();
+            return new NrVopsSupportInfo(in);
+        }
+
+        @Override
+        public NrVopsSupportInfo[] newArray(int size) {
+            return new NrVopsSupportInfo[size];
+        }
+    };
+
+    /** @hide */
+    protected static NrVopsSupportInfo createFromParcelBody(Parcel in) {
+        return new NrVopsSupportInfo(in);
+    }
+
+    private NrVopsSupportInfo(Parcel in) {
+        mVopsSupport = in.readInt();
+        mEmcSupport = in.readInt();
+        mEmfSupport = in.readInt();
+    }
+}
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 17af463..a527e8d 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -2219,7 +2219,7 @@
                 ret = iccISms.getSmsCapacityOnIccForSubscriber(getSubscriptionId());
             }
         } catch (RemoteException ex) {
-            throw new RuntimeException(ex);
+            Log.e(TAG, "getSmsCapacityOnIcc() RemoteException", ex);
         }
         return ret;
     }
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index f7580d7..a46621a 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -630,7 +630,7 @@
                     D2D_SHARING_STARRED_CONTACTS,
                     D2D_SHARING_ALL
             })
-    public @interface DeviceToDeviceStatusSharing {}
+    public @interface DeviceToDeviceStatusSharingPreference {}
 
     /**
      * TelephonyProvider column name for device to device sharing status.
@@ -3415,29 +3415,31 @@
      * app uses this method to indicate with whom they wish to share device to device status
      * information.
      * @param sharing the status sharing preference
-     * @param subId the unique Subscription ID in database
+     * @param subscriptionId the unique Subscription ID in database
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
-    public void setDeviceToDeviceStatusSharing(@DeviceToDeviceStatusSharing int sharing,
-            int subId) {
+    public void setDeviceToDeviceStatusSharingPreference(
+            @DeviceToDeviceStatusSharingPreference int sharing, int subscriptionId) {
         if (VDBG) {
-            logd("[setDeviceToDeviceStatusSharing] + sharing: " + sharing + " subId: " + subId);
+            logd("[setDeviceToDeviceStatusSharing] + sharing: " + sharing + " subId: "
+                    + subscriptionId);
         }
-        setSubscriptionPropertyHelper(subId, "setDeviceToDeviceSharingStatus",
-                (iSub)->iSub.setDeviceToDeviceStatusSharing(sharing, subId));
+        setSubscriptionPropertyHelper(subscriptionId, "setDeviceToDeviceSharingStatus",
+                (iSub)->iSub.setDeviceToDeviceStatusSharing(sharing, subscriptionId));
     }
 
     /**
      * Returns the user-chosen device to device status sharing preference
-     * @param subId Subscription id of subscription
+     * @param subscriptionId Subscription id of subscription
      * @return The device to device status sharing preference
      */
-    public @DeviceToDeviceStatusSharing int getDeviceToDeviceStatusSharing(int subId) {
+    public @DeviceToDeviceStatusSharingPreference int getDeviceToDeviceStatusSharingPreference(
+            int subscriptionId) {
         if (VDBG) {
-            logd("[getDeviceToDeviceStatusSharing] + subId: " + subId);
+            logd("[getDeviceToDeviceStatusSharing] + subId: " + subscriptionId);
         }
-        return getIntegerSubscriptionProperty(subId, D2D_STATUS_SHARING, D2D_SHARING_DISABLED,
-                mContext);
+        return getIntegerSubscriptionProperty(subscriptionId, D2D_STATUS_SHARING,
+                D2D_SHARING_DISABLED, mContext);
     }
 
     /**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f06b538..d3246ca 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -11037,6 +11037,25 @@
     }
 
     /**
+     * Determines the {@link PhoneAccountHandle} associated with this TelephonyManager.
+     *
+     * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
+     * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
+     *
+     * <p>Requires Permission android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE or that the
+     * calling app has carrier privileges (see {@link #hasCarrierPrivileges})
+     *
+     * @return The {@link PhoneAccountHandle} associated with the TelphonyManager, or {@code null}
+     * if there is no associated {@link PhoneAccountHandle}; this can happen if the subscription is
+     * data-only or an opportunistic subscription.
+     */
+    @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public @Nullable PhoneAccountHandle getPhoneAccountHandle() {
+        return getPhoneAccountHandleForSubscriptionId(getSubId());
+    }
+
+    /**
      * Determines the {@link PhoneAccountHandle} associated with a subscription Id.
      *
      * @param subscriptionId The subscription Id to check.
@@ -12388,12 +12407,6 @@
      * "Data capable" means that this device supports packet-switched
      * data connections over the telephony network.
      * <p>
-     * Note: the meaning of this flag is subtly different from the
-     * PackageManager.FEATURE_TELEPHONY system feature, which is available
-     * on any device with a telephony radio, even if the device is
-     * voice-only.
-     *
-     * @hide
      */
     public boolean isDataCapable() {
         if (mContext == null) return true;
@@ -14928,12 +14941,23 @@
     public static final String CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE =
             "CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE";
 
+    /**
+     * Indicates whether a data throttling request sent with {@link #sendThermalMitigationRequest}
+     * is supported. See comments on {@link #sendThermalMitigationRequest} for more information.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING =
+            "CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING";
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @StringDef(prefix = "CAPABILITY_", value = {
             CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE,
             CAPABILITY_ALLOWED_NETWORK_TYPES_USED,
-            CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE
+            CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE,
+            CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING,
     })
     public @interface RadioInterfaceCapability {}
 
@@ -15043,11 +15067,24 @@
      * and can be used at any time during data throttling to hold onto the current level of data
      * throttling.
      *
+     * <p> If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}({@link
+     * #CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING}) returns false, then sending a {@link
+     * DataThrottlingRequest#DATA_THROTTLING_ACTION_HOLD}, {@link
+     * DataThrottlingRequest#DATA_THROTTLING_ACTION_THROTTLE_SECONDARY_CARRIER}, or {@link
+     * DataThrottlingRequest#DATA_THROTTLING_ACTION_THROTTLE_PRIMARY_CARRIER} will result in {@link
+     * IllegalArgumentException} being thrown. However, on devices that do not
+     * support data throttling, {@link
+     * DataThrottlingRequest#DATA_THROTTLING_ACTION_NO_DATA_THROTTLING} can still be requested in
+     * order to undo the mitigations above it (i.e {@link
+     * ThermalMitigationRequest#THERMAL_MITIGATION_ACTION_VOICE_ONLY} and/or {@link
+     * ThermalMitigationRequest#THERMAL_MITIGATION_ACTION_RADIO_OFF}).
+     *
      * @param thermalMitigationRequest Thermal mitigation request. See {@link
      * ThermalMitigationRequest} for details.
      *
      * @throws IllegalStateException if the Telephony process is not currently available.
-     * @throws IllegalArgumentException if the thermalMitigationRequest had invalid parameters.
+     * @throws IllegalArgumentException if the thermalMitigationRequest had invalid parameters or
+     * if the device's modem does not support data throttling.
      *
      * @hide
      */
diff --git a/telephony/java/android/telephony/VopsSupportInfo.aidl b/telephony/java/android/telephony/VopsSupportInfo.aidl
new file mode 100644
index 0000000..31c608f
--- /dev/null
+++ b/telephony/java/android/telephony/VopsSupportInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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.telephony;
+
+parcelable VopsSupportInfo;
diff --git a/telephony/java/android/telephony/VopsSupportInfo.java b/telephony/java/android/telephony/VopsSupportInfo.java
new file mode 100644
index 0000000..f89bfa9
--- /dev/null
+++ b/telephony/java/android/telephony/VopsSupportInfo.java
@@ -0,0 +1,103 @@
+/*
+ * 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.telephony;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
+
+/**
+ * Abstract base class for the information related to network VoPS support.
+ * This is the base class for XxVopsSupportInfo which represent VoPS support
+ * information for specific network access techonology.
+ * @hide
+ */
+@SuppressLint("ParcelNotFinal")
+@SystemApi
+public abstract class VopsSupportInfo implements Parcelable {
+
+    /**
+     * @hide
+     */
+    public VopsSupportInfo() {}
+
+    /**
+     * Returns whether VoPS is supported by the network
+     */
+    public abstract boolean isVopsSupported();
+
+    /**
+     * Returns whether emergency service is supported by the network
+     */
+    public abstract boolean isEmergencyServiceSupported();
+
+    /**
+     * Returns whether emergency service fallback is supported by the network
+     */
+    public abstract boolean isEmergencyServiceFallbackSupported();
+
+    /**
+     * Implement the Parcelable interface
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface */
+    @Override
+    public abstract void writeToParcel(@NonNull Parcel dest, int flags);
+
+    /**
+     * Used by child classes for parceling.
+     *
+     * @hide
+     */
+    protected void writeToParcel(@NonNull Parcel dest, int flags, int type) {
+        dest.writeInt(type);
+    }
+
+    /** Implement the Parcelable interface */
+    public static final @android.annotation.NonNull Creator<VopsSupportInfo> CREATOR =
+            new Creator<VopsSupportInfo>() {
+        @Override
+        public VopsSupportInfo createFromParcel(Parcel in) {
+                int type = in.readInt();
+                switch (type) {
+                    case AccessNetworkType.EUTRAN:
+                        return LteVopsSupportInfo.createFromParcelBody(in);
+                    case AccessNetworkType.NGRAN:
+                        return NrVopsSupportInfo.createFromParcelBody(in);
+                    default: throw new RuntimeException("Bad VopsSupportInfo Parcel");
+                }
+        }
+
+        @Override
+        public VopsSupportInfo[] newArray(int size) {
+            return new VopsSupportInfo[size];
+        }
+    };
+
+    @Override
+    public abstract int hashCode();
+
+    @Override
+    public abstract boolean equals(Object o);
+}
diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
index 52d0f03..a133ead 100644
--- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
@@ -29,7 +29,9 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Contains the User Capability Exchange capabilities corresponding to a contact's URI.
@@ -110,7 +112,6 @@
     /**
      * Builder to help construct {@link RcsContactUceCapability} instances when capabilities were
      * queried through SIP OPTIONS.
-     * @hide
      */
     public static final class OptionsBuilder {
 
@@ -151,7 +152,7 @@
          * @param tags the list of the supported feature tags
          * @return this OptionBuilder
          */
-        public @NonNull OptionsBuilder addFeatureTags(@NonNull List<String> tags) {
+        public @NonNull OptionsBuilder addFeatureTags(@NonNull Set<String> tags) {
             mCapabilities.mFeatureTags.addAll(tags);
             return this;
         }
@@ -220,7 +221,7 @@
     private @CapabilityMechanism int mCapabilityMechanism;
     private @RequestResult int mRequestResult;
 
-    private final List<String> mFeatureTags = new ArrayList<>();
+    private final Set<String> mFeatureTags = new HashSet<>();
     private final List<RcsContactPresenceTuple> mPresenceTuples = new ArrayList<>();
 
     private RcsContactUceCapability(@NonNull Uri contactUri, @CapabilityMechanism int mechanism,
@@ -235,7 +236,9 @@
         mCapabilityMechanism = in.readInt();
         mSourceType = in.readInt();
         mRequestResult = in.readInt();
-        in.readStringList(mFeatureTags);
+        List<String> featureTagList = new ArrayList<>();
+        in.readStringList(featureTagList);
+        mFeatureTags.addAll(featureTagList);
         in.readParcelableList(mPresenceTuples, RcsContactPresenceTuple.class.getClassLoader());
     }
 
@@ -245,7 +248,7 @@
         out.writeInt(mCapabilityMechanism);
         out.writeInt(mSourceType);
         out.writeInt(mRequestResult);
-        out.writeStringList(mFeatureTags);
+        out.writeStringList(new ArrayList<>(mFeatureTags));
         out.writeParcelableList(mPresenceTuples, flags);
     }
 
@@ -285,7 +288,20 @@
         if (mCapabilityMechanism != CAPABILITY_MECHANISM_OPTIONS) {
             return Collections.emptyList();
         }
-        return Collections.unmodifiableList(mFeatureTags);
+        return Collections.unmodifiableList(new ArrayList<>(mFeatureTags));
+    }
+
+    /**
+     * @return The feature tags present in the OPTIONS response from the network.
+     * <p>
+     * Note: this is only populated if {@link #getCapabilityMechanism} is
+     * {@link RcsContactUceCapability#CAPABILITY_MECHANISM_OPTIONS}
+     */
+    public @NonNull Set<String> getFeatureTags() {
+        if (mCapabilityMechanism != CAPABILITY_MECHANISM_OPTIONS) {
+            return Collections.emptySet();
+        }
+        return Collections.unmodifiableSet(mFeatureTags);
     }
 
     /**
diff --git a/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
index a217d13..c3d7325 100644
--- a/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
+++ b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
@@ -26,7 +26,8 @@
 import android.telephony.ims.stub.CapabilityExchangeEventListener;
 import android.util.Log;
 
-import java.util.List;
+import java.util.ArrayList;
+import java.util.Set;
 
 /**
  * The ICapabilityExchangeEventListener wrapper class to store the listener which is registered by
@@ -84,7 +85,7 @@
      * request to the framework.
      */
     public void onRemoteCapabilityRequest(@NonNull Uri contactUri,
-            @NonNull List<String> remoteCapabilities, @NonNull OptionsRequestCallback callback)
+            @NonNull Set<String> remoteCapabilities, @NonNull OptionsRequestCallback callback)
             throws ImsException {
         ICapabilityExchangeEventListener listener = mListenerBinder;
         if (listener == null) {
@@ -114,7 +115,8 @@
         };
 
         try {
-            listener.onRemoteCapabilityRequest(contactUri, remoteCapabilities, internalCallback);
+            listener.onRemoteCapabilityRequest(contactUri, new ArrayList<>(remoteCapabilities),
+                    internalCallback);
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Remote capability request exception: " + e);
             throw new ImsException("Remote is not available",
diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java
index 85703f8..6315b24 100644
--- a/telephony/java/android/telephony/ims/feature/RcsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java
@@ -47,6 +47,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.HashSet;
 import java.util.List;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.CompletableFuture;
@@ -145,8 +146,8 @@
                 throws RemoteException {
             OptionsResponseCallback callbackWrapper = new RcsOptionsResponseAidlWrapper(callback);
             executeMethodAsync(() -> mReference.getCapabilityExchangeImplBaseInternal()
-                    .sendOptionsCapabilityRequest(contactUri, myCapabilities, callbackWrapper),
-                    "sendOptionsCapabilityRequest");
+                    .sendOptionsCapabilityRequest(contactUri, new HashSet<>(myCapabilities),
+                        callbackWrapper), "sendOptionsCapabilityRequest");
         }
 
         // Call the methods with a clean calling identity on the executor and wait indefinitely for
diff --git a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
index 6295548..a3be8da 100644
--- a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
+++ b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
@@ -26,7 +26,7 @@
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.feature.RcsFeature;
 
-import java.util.List;
+import java.util.Set;
 
 /**
  * The interface that is used by the framework to listen to events from the vendor RCS stack
@@ -98,7 +98,8 @@
      * {@link OptionsRequestCallback#onRespondToCapabilityRequestWithError}.
      * @param contactUri The URI associated with the remote contact that is
      * requesting capabilities.
-     * @param remoteCapabilities The remote contact's capability information.
+     * @param remoteCapabilities The remote contact's capability information. The capability
+     * information is in the format defined in RCC.07 section 2.6.1.3.
      * @param callback The callback of this request which is sent from the remote user.
      * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is not
      * currently connected to the framework. This can happen if the {@link RcsFeature} is not
@@ -107,6 +108,6 @@
      * cases when the Telephony stack has crashed.
      */
     void onRemoteCapabilityRequest(@NonNull Uri contactUri,
-            @NonNull List<String> remoteCapabilities,
+            @NonNull Set<String> remoteCapabilities,
             @NonNull OptionsRequestCallback callback) throws ImsException;
 }
diff --git a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
index 03e17fb..25b9446 100644
--- a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
@@ -33,6 +33,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Collection;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.Executor;
 
 /**
@@ -433,6 +434,7 @@
      * @param contactUri The URI of the remote user that we wish to get the capabilities of.
      * @param myCapabilities The capabilities of this device to send to the remote user.
      * @param callback The callback of this request which is sent from the remote user.
+     * @hide
      */
     // executor used is defined in the constructor.
     @SuppressLint("ExecutorRegistration")
@@ -446,4 +448,27 @@
             // Do not do anything, this is a stub implementation.
         }
     }
+
+    /**
+     * Push one's own capabilities to a remote user via the SIP OPTIONS presence exchange mechanism
+     * in order to receive the capabilities of the remote user in response.
+     * <p>
+     * The implementer must use {@link OptionsResponseCallback} to send the response of
+     * this query from the network back to the framework.
+     * @param contactUri The URI of the remote user that we wish to get the capabilities of.
+     * @param myCapabilities The capabilities of this device to send to the remote user.
+     * @param callback The callback of this request which is sent from the remote user.
+     */
+    // executor used is defined in the constructor.
+    @SuppressLint("ExecutorRegistration")
+    public void sendOptionsCapabilityRequest(@NonNull Uri contactUri,
+            @NonNull Set<String> myCapabilities, @NonNull OptionsResponseCallback callback) {
+        // Stub - to be implemented by service
+        Log.w(LOG_TAG, "sendOptionsCapabilityRequest called with no implementation.");
+        try {
+            callback.onCommandError(COMMAND_CODE_NOT_SUPPORTED);
+        } catch (ImsException e) {
+            // Do not do anything, this is a stub implementation.
+        }
+    }
 }
diff --git a/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
index b9b347b..c1a86b3 100644
--- a/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
+++ b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
@@ -87,7 +87,7 @@
         assertEquals(down.id, eventId)
 
         // Now send CALLBACK_INPUT to the assigner. It should provide the latest motion event
-        assigner.onChoreographerCallback()
+        assigner.notifyFrameProcessed()
         eventId = assigner.processEvent(move3)
         assertEquals(move3.id, eventId)
         eventId = assigner.processEvent(move4)
@@ -122,7 +122,7 @@
         eventId = assigner.processEvent(up)
         // DOWN is only sticky for Motions, not for keys
         assertEquals(up.id, eventId)
-        assigner.onChoreographerCallback()
+        assigner.notifyFrameProcessed()
         val down2 = createKeyEvent(KeyEvent.ACTION_DOWN, 22)
         eventId = assigner.processEvent(down2)
         assertEquals(down2.id, eventId)
diff --git a/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt
index 4f95ce5..b134fe7 100644
--- a/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt
+++ b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt
@@ -17,6 +17,7 @@
 package com.android.test.input
 
 import android.os.HandlerThread
+import android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS
 import android.os.Looper
 import android.view.InputChannel
 import android.view.InputEvent
@@ -24,7 +25,8 @@
 import android.view.InputEventSender
 import android.view.KeyEvent
 import android.view.MotionEvent
-import java.util.concurrent.CountDownLatch
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
 import org.junit.Assert.assertEquals
 import org.junit.After
 import org.junit.Before
@@ -44,41 +46,44 @@
     assertEquals(expected.displayId, received.displayId)
 }
 
+private fun <T> getEvent(queue: LinkedBlockingQueue<T>): T {
+    try {
+        return queue.poll(DEFAULT_DISPATCHING_TIMEOUT_MILLIS.toLong(), TimeUnit.MILLISECONDS)
+    } catch (e: InterruptedException) {
+        throw RuntimeException("Unexpectedly interrupted while waiting for event")
+    }
+}
+
 class TestInputEventReceiver(channel: InputChannel, looper: Looper) :
         InputEventReceiver(channel, looper) {
-    companion object {
-        const val TAG = "TestInputEventReceiver"
-    }
-
-    var lastEvent: InputEvent? = null
+    private val mInputEvents = LinkedBlockingQueue<InputEvent>()
 
     override fun onInputEvent(event: InputEvent) {
-        lastEvent = when (event) {
-            is KeyEvent -> KeyEvent.obtain(event)
-            is MotionEvent -> MotionEvent.obtain(event)
+        when (event) {
+            is KeyEvent -> mInputEvents.put(KeyEvent.obtain(event))
+            is MotionEvent -> mInputEvents.put(MotionEvent.obtain(event))
             else -> throw Exception("Received $event is neither a key nor a motion")
         }
         finishInputEvent(event, true /*handled*/)
     }
+
+    fun getInputEvent(): InputEvent {
+        return getEvent(mInputEvents)
+    }
 }
 
 class TestInputEventSender(channel: InputChannel, looper: Looper) :
         InputEventSender(channel, looper) {
-    companion object {
-        const val TAG = "TestInputEventSender"
-    }
-    data class FinishedResult(val seq: Int, val handled: Boolean)
+    data class FinishedSignal(val seq: Int, val handled: Boolean)
 
-    private var mFinishedSignal = CountDownLatch(1)
+    private val mFinishedSignals = LinkedBlockingQueue<FinishedSignal>()
+
     override fun onInputEventFinished(seq: Int, handled: Boolean) {
-        finishedResult = FinishedResult(seq, handled)
-        mFinishedSignal.countDown()
+        mFinishedSignals.put(FinishedSignal(seq, handled))
     }
-    lateinit var finishedResult: FinishedResult
 
-    fun waitForFinish() {
-        mFinishedSignal.await()
-        mFinishedSignal = CountDownLatch(1) // Ready for next event
+    fun getFinishedSignal(): FinishedSignal {
+        return getEvent(mFinishedSignals)
     }
 }
 
@@ -111,13 +116,13 @@
                 KeyEvent.KEYCODE_A, 0 /*repeat*/)
         val seq = 10
         mSender.sendInputEvent(seq, key)
-        mSender.waitForFinish()
+        val receivedKey = mReceiver.getInputEvent() as KeyEvent
+        val finishedSignal = mSender.getFinishedSignal()
 
         // Check receiver
-        assertKeyEvent(key, mReceiver.lastEvent!! as KeyEvent)
+        assertKeyEvent(key, receivedKey)
 
         // Check sender
-        assertEquals(seq, mSender.finishedResult.seq)
-        assertEquals(true, mSender.finishedResult.handled)
+        assertEquals(TestInputEventSender.FinishedSignal(seq, handled = true), finishedSignal)
     }
 }
diff --git a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
index a4d8353..fd126ad 100644
--- a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
+++ b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
@@ -44,7 +44,7 @@
             setPartialConnectivityAcceptable(false)
             setUnvalidatedConnectivityAcceptable(true)
         }.build()
-        assertParcelSane(config, 10)
+        assertParcelSane(config, 12)
     }
 
     @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index 26ae099..9946515 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -515,11 +515,22 @@
         assertFalse(nc1.equalsNetCapabilities(nc2));
         nc2.addUnwantedCapability(NET_CAPABILITY_INTERNET);
         assertTrue(nc1.equalsNetCapabilities(nc2));
+        if (isAtLeastS()) {
+            // Remove a required capability doesn't affect unwanted capabilities.
+            // This is a behaviour change from S.
+            nc1.removeCapability(NET_CAPABILITY_INTERNET);
+            assertTrue(nc1.equalsNetCapabilities(nc2));
 
-        nc1.removeCapability(NET_CAPABILITY_INTERNET);
-        assertFalse(nc1.equalsNetCapabilities(nc2));
-        nc2.removeCapability(NET_CAPABILITY_INTERNET);
-        assertTrue(nc1.equalsNetCapabilities(nc2));
+            nc1.removeUnwantedCapability(NET_CAPABILITY_INTERNET);
+            assertFalse(nc1.equalsNetCapabilities(nc2));
+            nc2.removeUnwantedCapability(NET_CAPABILITY_INTERNET);
+            assertTrue(nc1.equalsNetCapabilities(nc2));
+        } else {
+            nc1.removeCapability(NET_CAPABILITY_INTERNET);
+            assertFalse(nc1.equalsNetCapabilities(nc2));
+            nc2.removeCapability(NET_CAPABILITY_INTERNET);
+            assertTrue(nc1.equalsNetCapabilities(nc2));
+        }
     }
 
     @Test
@@ -580,11 +591,21 @@
         // This will effectively move NOT_ROAMING capability from required to unwanted for nc1.
         nc1.addUnwantedCapability(NET_CAPABILITY_NOT_ROAMING);
 
-        nc2.combineCapabilities(nc1);
-        // We will get this capability in both requested and unwanted lists thus this request
-        // will never be satisfied.
-        assertTrue(nc2.hasCapability(NET_CAPABILITY_NOT_ROAMING));
-        assertTrue(nc2.hasUnwantedCapability(NET_CAPABILITY_NOT_ROAMING));
+        if (isAtLeastS()) {
+            // From S, it is not allowed to have the same capability in both wanted and
+            // unwanted list.
+            assertThrows(IllegalArgumentException.class, () -> nc2.combineCapabilities(nc1));
+            // Remove unwanted capability to continue other tests.
+            nc1.removeUnwantedCapability(NET_CAPABILITY_NOT_ROAMING);
+        } else {
+            nc2.combineCapabilities(nc1);
+            // We will get this capability in both requested and unwanted lists thus this request
+            // will never be satisfied.
+            assertTrue(nc2.hasCapability(NET_CAPABILITY_NOT_ROAMING));
+            assertTrue(nc2.hasUnwantedCapability(NET_CAPABILITY_NOT_ROAMING));
+            // For R or below, remove unwanted capability via removeCapability.
+            nc1.removeCapability(NET_CAPABILITY_NOT_ROAMING);
+        }
 
         nc1.setSSID(TEST_SSID);
         nc2.combineCapabilities(nc1);
diff --git a/tests/net/integration/src/android/net/TestNetworkStackClient.kt b/tests/net/integration/src/android/net/TestNetworkStackClient.kt
index 01eb514..61ef5bd 100644
--- a/tests/net/integration/src/android/net/TestNetworkStackClient.kt
+++ b/tests/net/integration/src/android/net/TestNetworkStackClient.kt
@@ -19,6 +19,7 @@
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
+import android.net.networkstack.NetworkStackClientBase
 import android.os.IBinder
 import com.android.server.net.integrationtests.TestNetworkStackService
 import org.mockito.Mockito.any
@@ -29,28 +30,22 @@
 
 const val TEST_ACTION_SUFFIX = ".Test"
 
-class TestNetworkStackClient(context: Context) : NetworkStackClient(TestDependencies(context)) {
+class TestNetworkStackClient(private val context: Context) : NetworkStackClientBase() {
     // TODO: consider switching to TrackRecord for more expressive checks
     private val lastCallbacks = HashMap<Network, INetworkMonitorCallbacks>()
+    private val moduleConnector = ConnectivityModuleConnector { _, action, _, _ ->
+        val intent = Intent(action)
+        val serviceName = TestNetworkStackService::class.qualifiedName
+                ?: fail("TestNetworkStackService name not found")
+        intent.component = ComponentName(context.packageName, serviceName)
+        return@ConnectivityModuleConnector intent
+    }.also { it.init(context) }
 
-    private class TestDependencies(private val context: Context) : Dependencies {
-        override fun addToServiceManager(service: IBinder) = Unit
-        override fun checkCallerUid() = Unit
-
-        override fun getConnectivityModuleConnector(): ConnectivityModuleConnector {
-            return ConnectivityModuleConnector { _, _, _, inSystemProcess ->
-                getNetworkStackIntent(inSystemProcess)
-            }.also { it.init(context) }
-        }
-
-        private fun getNetworkStackIntent(inSystemProcess: Boolean): Intent? {
-            // Simulate out-of-system-process config: in-process service not found (null intent)
-            if (inSystemProcess) return null
-            val intent = Intent(INetworkStackConnector::class.qualifiedName + TEST_ACTION_SUFFIX)
-            val serviceName = TestNetworkStackService::class.qualifiedName
-                    ?: fail("TestNetworkStackService name not found")
-            intent.component = ComponentName(context.packageName, serviceName)
-            return intent
+    fun start() {
+        moduleConnector.startModuleService(
+                INetworkStackConnector::class.qualifiedName + TEST_ACTION_SUFFIX,
+                NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) { connector ->
+            onNetworkStackConnected(INetworkStackConnector.Stub.asInterface(connector))
         }
     }
 
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index db49e0b..14dddcbd 100644
--- a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -157,7 +157,6 @@
         doReturn(IntArray(0)).`when`(systemConfigManager).getSystemPermissionUids(anyString())
 
         networkStackClient = TestNetworkStackClient(realContext)
-        networkStackClient.init()
         networkStackClient.start()
 
         service = TestConnectivityService(makeDependencies())
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 8a50141..db1334a 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -216,7 +216,6 @@
 import android.net.NetworkScore;
 import android.net.NetworkSpecifier;
 import android.net.NetworkStack;
-import android.net.NetworkStackClient;
 import android.net.NetworkStateSnapshot;
 import android.net.NetworkTestResultParcelable;
 import android.net.OemNetworkPreferences;
@@ -236,6 +235,7 @@
 import android.net.VpnManager;
 import android.net.VpnTransportInfo;
 import android.net.metrics.IpConnectivityLog;
+import android.net.networkstack.NetworkStackClientBase;
 import android.net.resolv.aidl.Nat64PrefixEventParcel;
 import android.net.resolv.aidl.PrivateDnsValidationEventParcel;
 import android.net.shared.NetworkMonitorUtils;
@@ -446,7 +446,7 @@
     @Mock NetworkStatsManager mStatsManager;
     @Mock IDnsResolver mMockDnsResolver;
     @Mock INetd mMockNetd;
-    @Mock NetworkStackClient mNetworkStack;
+    @Mock NetworkStackClientBase mNetworkStack;
     @Mock PackageManager mPackageManager;
     @Mock UserManager mUserManager;
     @Mock NotificationManager mNotificationManager;
@@ -1178,11 +1178,6 @@
         }
 
         @Override
-        public int getNetId() {
-            return (mMockNetworkAgent == null) ? NETID_UNSET : mMockNetworkAgent.getNetwork().netId;
-        }
-
-        @Override
         public int getActiveVpnType() {
             return mVpnType;
         }
@@ -1206,10 +1201,12 @@
                     mNetworkCapabilities);
             mMockNetworkAgent.waitForIdle(TIMEOUT_MS);
 
-            verify(mMockNetd, times(1)).networkAddUidRanges(eq(mMockVpn.getNetId()),
+            final int expectedNetId = mMockVpn.getNetwork() == null ? NETID_UNSET
+                    : mMockVpn.getNetwork().getNetId();
+            verify(mMockNetd, times(1)).networkAddUidRanges(eq(expectedNetId),
                     eq(toUidRangeStableParcels(uids)));
             verify(mMockNetd, never())
-                    .networkRemoveUidRanges(eq(mMockVpn.getNetId()), any());
+                    .networkRemoveUidRanges(eq(expectedNetId), any());
             mAgentRegistered = true;
             updateState(NetworkInfo.DetailedState.CONNECTED, "registerAgent");
             mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities());
@@ -7488,6 +7485,9 @@
         final NetworkRequest vpnUidRequest = new NetworkRequest.Builder().build();
         registerNetworkCallbackAsUid(vpnUidRequest, vpnUidCallback, VPN_UID);
 
+        final TestNetworkCallback vpnUidDefaultCallback = new TestNetworkCallback();
+        registerDefaultNetworkCallbackAsUid(vpnUidDefaultCallback, VPN_UID);
+
         final int uid = Process.myUid();
         final int userId = UserHandle.getUserId(uid);
         final ArrayList<String> allowList = new ArrayList<>();
@@ -7506,6 +7506,7 @@
         callback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
         defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
         vpnUidCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        vpnUidDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertNull(mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
@@ -7518,6 +7519,7 @@
         callback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
         defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
         vpnUidCallback.assertNoCallback();
+        vpnUidDefaultCallback.assertNoCallback();
         expectNetworkRejectNonSecureVpn(inOrder, false, firstHalf, secondHalf);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -7532,6 +7534,7 @@
         callback.assertNoCallback();
         defaultCallback.assertNoCallback();
         vpnUidCallback.assertNoCallback();
+        vpnUidDefaultCallback.assertNoCallback();
 
         // The following requires that the UID of this test package is greater than VPN_UID. This
         // is always true in practice because a plain AOSP build with no apps installed has almost
@@ -7552,6 +7555,7 @@
         callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         defaultCallback.assertNoCallback();
         vpnUidCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+        vpnUidDefaultCallback.assertNoCallback();
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7572,6 +7576,7 @@
         defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent);
         assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent);
         vpnUidCallback.assertNoCallback();
+        vpnUidDefaultCallback.assertNoCallback();
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertNull(mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
@@ -7583,6 +7588,7 @@
         defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
         assertBlockedCallbackInAnyOrder(callback, false, mWiFiNetworkAgent, mCellNetworkAgent);
         vpnUidCallback.assertNoCallback();
+        vpnUidDefaultCallback.assertNoCallback();
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7597,6 +7603,7 @@
         callback.assertNoCallback();
         defaultCallback.assertNoCallback();
         vpnUidCallback.assertNoCallback();
+        vpnUidDefaultCallback.assertNoCallback();
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7608,6 +7615,7 @@
         callback.assertNoCallback();
         defaultCallback.assertNoCallback();
         vpnUidCallback.assertNoCallback();
+        vpnUidDefaultCallback.assertNoCallback();
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7620,6 +7628,7 @@
         defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent);
         assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent);
         vpnUidCallback.assertNoCallback();
+        vpnUidDefaultCallback.assertNoCallback();
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertNull(mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
@@ -7630,6 +7639,7 @@
         assertUidRangesUpdatedForMyUid(true);
         defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
         vpnUidCallback.assertNoCallback();  // vpnUidCallback has NOT_VPN capability.
+        vpnUidDefaultCallback.assertNoCallback();  // VPN does not apply to VPN_UID
         assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7640,11 +7650,14 @@
         mMockVpn.disconnect();
         defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
         defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
+        vpnUidCallback.assertNoCallback();
+        vpnUidDefaultCallback.assertNoCallback();
         assertNull(mCm.getActiveNetwork());
 
         mCm.unregisterNetworkCallback(callback);
         mCm.unregisterNetworkCallback(defaultCallback);
         mCm.unregisterNetworkCallback(vpnUidCallback);
+        mCm.unregisterNetworkCallback(vpnUidDefaultCallback);
     }
 
     private void setupLegacyLockdownVpn() {
@@ -9745,11 +9758,14 @@
                 exemptUidCaptor.capture());
         assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid);
 
+        final int expectedNetId = mMockVpn.getNetwork() == null ? NETID_UNSET
+                : mMockVpn.getNetwork().getNetId();
+
         if (add) {
-            inOrder.verify(mMockNetd, times(1)).networkAddUidRanges(eq(mMockVpn.getNetId()),
+            inOrder.verify(mMockNetd, times(1)).networkAddUidRanges(eq(expectedNetId),
                     eq(toUidRangeStableParcels(vpnRanges)));
         } else {
-            inOrder.verify(mMockNetd, times(1)).networkRemoveUidRanges(eq(mMockVpn.getNetId()),
+            inOrder.verify(mMockNetd, times(1)).networkRemoveUidRanges(eq(expectedNetId),
                     eq(toUidRangeStableParcels(vpnRanges)));
         }